Альтернативы статическим методам в Java - PullRequest
13 голосов
/ 30 сентября 2008

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

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

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

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

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 реализуют метод класса (или, если это не так, константу класса!)?

Ответы [ 9 ]

4 голосов
/ 30 сентября 2008

Статика - это не то, что нужно использовать здесь.

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

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

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

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

2 голосов
/ 30 сентября 2008

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

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

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

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

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;

    }
}

То есть сопоставить все операторы с картой. «Очевидный» следующий шаг к этому - загрузить карту из внешнего ресурса, такого как файл свойств, или 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, которая работает быстрее или делает что-то еще, не изменяя ваш клиентский код.

1 голос
/ 30 сентября 2008

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

1 голос
/ 30 сентября 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....."; }
}

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

0 голосов
/ 30 сентября 2008

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

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

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

0 голосов
/ 30 сентября 2008

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

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

0 голосов
/ 30 сентября 2008

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

0 голосов
/ 30 сентября 2008

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

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

0 голосов
/ 30 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...