2008-09-30 15 views
13

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

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

+0

Я немного озадачен, почему вы пишете свою собственную ОРМ, когда есть отличные варианты, такие как iBatis и Hibernate. Кстати, мне кажется, вы пытаетесь заставить статический метод Java работать как метод класса Ruby. Если это так, это не сработает :( – Alan 2008-09-30 05:51:27

ответ

2

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

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

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

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

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. 

...

Я надеюсь, что это помогает.

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

0

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

1

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

0

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

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

+0

Тогда вы должны взглянуть на ORM на языках, которые не поддерживают отражение, например, на C++. Потому что, насколько я знаю, все Java ORM (если не все) используют отражения в какой-то момент. – gizmo 2008-09-30 05:48:52

0

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

-1

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

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

0

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

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

См. Мой предыдущий вопрос (вопрос) здесь: How to handle static fields that vary by implementing class Я думал, что все ответы были очень хромыми и пропустили точку. Ваш вопрос намного лучше сформулирован.

1

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

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....."; } 
} 

Но это не очень хороший запах, чтобы объекты без какого-либо государства.

4

Static - неправильная вещь, которую следует использовать здесь.

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

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

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

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