Альтернативы статическим методам в Java

Я делаю мини ORM для Java-программы, которую пишу... для каждой таблицы в моей БД есть класс, все наследующий от ModelBase.

ModelBase является абстрактным и предоставляет набор статических методов для поиска и привязки объектов из базы данных, например:

public static ArrayList findAll(Class cast_to_class) {
  //build the sql query & execute it 
}

Таким образом, вы можете сделать что-то вроде ModelBase.findAll(Albums.class), чтобы получить список всех сохраненных альбомов. Моя проблема в том, что в этом статическом контексте мне нужно получить соответствующую строку sql из конкретного альбома класса. У меня не может быть статический метод, например

public class Album extends ModelBase {
  public static String getSelectSQL() { return "select * from albums.....";}
}

потому что в Java нет полиморфизма для статических методов. Но я не хочу делать getSelectSQL() методом экземпляра в Album, потому что тогда мне нужно создать его экземпляр только для того, чтобы получить строку, поведение которой действительно статично.

На данный момент findAll() использует отражение, чтобы получить соответствующий sql для рассматриваемого класса:

select_sql = (String)cast_to_class.getDeclaredMethod("getSelectSql", new Class[]{} ).invoke(null, null);

Но это довольно грубо.

Итак, есть идеи? Это общая проблема, с которой я сталкиваюсь снова и снова - невозможность указать абстрактные статические методы в классах или интерфейсах. Я знаю, почему полиморфизм статических методов не работает и не может работать, но это не мешает мне использовать его снова!

Есть ли какой-либо шаблон/конструкция, которая позволяет мне гарантировать, что конкретные подклассы X и Y реализуют метод класса (или, в противном случае, константу класса!)?


person dalyons    schedule 30.09.2008    source источник
comment
Я немного озадачен, почему вы пишете свой собственный ORM, когда есть отличные варианты, такие как iBatis и Hibernate. Кстати, мне кажется, вы пытаетесь заставить статический метод Java работать как метод класса Ruby. Если да, то не получится :(   -  person Alan    schedule 30.09.2008


Ответы (9)


Хотя я полностью согласен с тем, что «Статика — это неправильное использование здесь», я вроде как понимаю, что вы пытаетесь решить здесь. Тем не менее поведение экземпляра должно быть способом работы, но если вы настаиваете на том, что я бы сделал:

Начиная с вашего комментария «Мне нужно создать его экземпляр, чтобы получить строку, которая действительно статична в поведении»

Это не совсем правильно. Если вы хорошо выглядите, вы не меняете поведение своего базового класса, а просто меняете параметр для метода. Другими словами, вы меняете данные, а не алгоритм.

Наследование более полезно, когда новый подкласс хочет изменить способ работы метода, если вам просто нужно изменить «данные», которые класс использует для работы, возможно, такой подход поможет.

class ModelBase {
    // Initialize the queries
    private static Map<String,String> selectMap = new HashMap<String,String>(); static {
        selectMap.put( "Album", "select field_1, field_2 from album");
        selectMap.put( "Artist", "select field_1, field_2 from artist");
        selectMap.put( "Track", "select field_1, field_2 from track");
    }

    // Finds all the objects for the specified class...
    // Note: it is better to use "List" rather than "ArrayList" I'll explain this later.
    public static List findAll(Class classToFind ) {
        String sql = getSelectSQL( classToFind );
        results = execute( sql );
        //etc...
        return ....
    }

    // Return the correct select sql..
    private static String getSelectSQL( Class classToFind ){
        String statement = tableMap.get( classToFind.getSimpleName() );
        if( statement == null ) {
            throw new IllegalArgumentException("Class " + 
                 classToFind.getSimpleName + " is not mapped");
        }
        return statement;

    }
}

То есть сопоставьте все операторы с Map. «Очевидным» следующим шагом является загрузка карты из внешнего ресурса, такого как файл свойств, xml или даже (почему бы и нет) таблица базы данных, для дополнительной гибкости.

Таким образом, вы можете поддерживать своих клиентов класса (и себя) счастливыми, потому что вам не нужно «создавать экземпляр» для выполнения работы.

// Client usage:

...
List albums = ModelBase.findAll( Album.class );

...

Другой подход заключается в создании экземпляров сзади и сохранении интерфейса клиента нетронутым при использовании методов экземпляра, методы помечены как «защищенные», чтобы избежать внешнего вызова. По аналогии с предыдущим образцом вы также можете сделать это

// Second option, instance used under the hood.
class ModelBase {
    // Initialize the queries
    private static Map<String,ModelBase> daoMap = new HashMap<String,ModelBase>(); static {
        selectMap.put( "Album", new AlbumModel() );
        selectMap.put( "Artist", new ArtistModel());
        selectMap.put( "Track", new TrackModel());
    }

    // Finds all the objects for the specified class...
    // Note: it is better to use "List" rather than "ArrayList" I'll explain this later.
    public static List findAll(Class classToFind ) {
        String sql = getSelectSQL( classToFind );
        results = execute( sql );
        //etc...
        return ....
    }

    // Return the correct select sql..
    private static String getSelectSQL( Class classToFind ){
        ModelBase dao = tableMap.get( classToFind.getSimpleName() );
        if( statement == null ) {
            throw new IllegalArgumentException("Class " + 
                 classToFind.getSimpleName + " is not mapped");
        }
        return dao.selectSql();
    }
    // Instance class to be overrided... 
    // this is "protected" ... 
    protected abstract String selectSql();
}
class AlbumModel  extends ModelBase {
    public String selectSql(){
        return "select ... from album";
    }
}
class ArtistModel  extends ModelBase {
    public String selectSql(){
        return "select ... from artist";
    }
}
class TrackModel  extends ModelBase {
    public String selectSql(){
        return "select ... from track";
    }
}

И вам не нужно менять клиентский код, и вы по-прежнему обладаете силой полиморфизма.

// Client usage:

...
List albums = ModelBase.findAll( Album.class ); // Does not know , behind the scenes you use instances.

...

Надеюсь, это поможет.

Последнее замечание об использовании List и ArrayList. Всегда лучше программировать для интерфейса, чем для реализации, так вы сделаете свой код более гибким. Вы можете использовать другую реализацию List, которая быстрее или делает что-то еще, не меняя клиентский код.

person OscarRyz    schedule 30.09.2008

Статика здесь неуместна.

Концептуально статический неверен, потому что он предназначен только для служб, которые не соответствуют реальному объекту, физическому или концептуальному. У вас есть несколько таблиц, и каждая из них должна быть представлена ​​реальным объектом в системе, а не просто классом. Звучит немного теоретически, но, как мы увидим, у него есть реальные последствия.

Каждая таблица относится к разным классам, и это нормально. Поскольку у вас может быть только одна таблица каждой таблицы, ограничьте количество экземпляров каждого класса до одного (используйте флаг - не делайте его синглтоном). Заставьте программу создать экземпляр класса, прежде чем он получит доступ к таблице.

Теперь у вас есть несколько преимуществ. Вы можете использовать всю мощь наследования и переопределения, поскольку ваши методы больше не статичны. Вы можете использовать конструктор для любой инициализации, включая связывание SQL с таблицей (SQL, который ваши методы могут использовать позже). Это должно решить все ваши проблемы, описанные выше, или, по крайней мере, стать намного проще.

Кажется, что есть дополнительная работа по созданию объекта и дополнительной памяти, но это действительно тривиально по сравнению с преимуществами. Несколько байтов памяти для объекта не будут замечены, а для добавления нескольких вызовов конструктора потребуется, может быть, десять минут. Преимуществом этого является то, что код для инициализации любых таблиц не нужно запускать, если таблица не используется (конструктор не должен вызываться). Вы обнаружите, что это многое упрощает.

person DJClayworth    schedule 30.09.2008

Почему бы не использовать аннотации? Они очень хорошо подходят для того, что вы делаете: добавляете метаинформацию (в данном случае SQL-запрос) к классу.

person gizmo    schedule 30.09.2008

Как было предложено, вы можете использовать аннотации или переместить статические методы в фабричные объекты:

public abstract class BaseFactory<E> {
    public abstract String getSelectSQL();
    public List<E> findAll(Class<E> clazz) {
       // Use getSelectSQL();
    }
}

public class AlbumFactory extends BaseFactory<Album> {
    public String getSelectSQL() { return "select * from albums....."; }
}

Но не очень приятно иметь объекты без какого-либо состояния.

person Bruno De Fraine    schedule 30.09.2008

Если вы передаете класс в findAll, почему вы не можете передать класс в getSelectSQL в ModelBase?

person asterite    schedule 30.09.2008

asterite: вы имеете в виду, что getSelectSQL существует только в ModelBase и использует переданный класс для создания имени таблицы или чего-то в этом роде? Я не могу этого сделать, потому что некоторые модели имеют очень разные конструкции выбора, поэтому я не могу использовать универсальный «выбрать * из» + classToTableName();. И любая попытка получить информацию от моделей об их конструкции select сталкивается с той же проблемой, что и исходный вопрос - вам нужен экземпляр модели или какое-то причудливое отражение.

gizmo: Я обязательно загляну в аннотации. Хотя я не могу не задаться вопросом, что люди делали с этими проблемами до того, как появилось отражение?

person dalyons    schedule 30.09.2008
comment
Тогда вам следует взглянуть на ORM в языках, которые не поддерживают отражение, таких как C++. Потому что, насколько я знаю, почти все Java ORM (если не все) в какой-то момент используют отражения. - person gizmo; 30.09.2008
comment
Пожалуйста, делайте эти комментарии как... комментарии ;) Ответ только для ответа. Таким образом, gizmo и asterite будут редактировать свои ответы, чтобы завершить их, или оставят дополнительные комментарии, чтобы продолжить обсуждение, начатое вашими вопросами. - person VonC; 30.09.2008

Вы можете иметь свои методы SQL как методы экземпляра в отдельном классе.
Затем передайте объект модели в конструктор этого нового класса и вызовите его методы для получения SQL.

person Darren Greaves    schedule 30.09.2008

Вау, это гораздо лучший пример того, о чем я спрашивал ранее в более общих терминах, — как реализовать свойства или методы, которые являются статическими для каждого реализующего класса, таким образом, чтобы избежать дублирования, обеспечить статический доступ без необходимости создания экземпляра соответствующего класса и ощущения. 'Верно'.

Краткий ответ (Java или .NET): вы не можете. Более длинный ответ - вы можете, если не возражаете использовать аннотацию уровня класса (отражение) или создание экземпляра объекта (метод экземпляра), но ни один из них не является действительно «чистым».

См. мой предыдущий (связанный) вопрос здесь: Как обрабатывать статические поля, которые различаются в зависимости от класса реализации Я думал, что все ответы были действительно хромыми, и упустил суть. Ваш вопрос сформулирован гораздо лучше.

person Ewan Makepeace    schedule 30.09.2008

Я согласен с Gizmo: вы либо смотрите на аннотации, либо на какой-то файл конфигурации. Я бы взглянул на Hibernate и другие фреймворки ORM (и, возможно, даже на такие библиотеки, как log4j!), чтобы увидеть, как они справляются с загрузкой метаинформации на уровне класса.

Не все можно или нужно делать программно, я чувствую, что это может быть один из таких случаев.

person Tim Mooney    schedule 30.09.2008