Альтернативы статическим методам в Java
Я делаю мини-ORM для Java-программы, которую я пишу... для каждой таблицы в моем db есть класс, все наследуемые от ModelBase
.
ModelBase
является абстрактным и предоставляет кучу статических методов для поиска и привязки объектов из db, например:
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 реализуют метод класса (или, если это не так, константа класса!)?
Ответы
Ответ 1
Хотя я полностью согласен в том, что "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.
...
Надеюсь, это поможет.
Последняя заметка об использовании List vs. ArrayList. Всегда лучше программировать интерфейс, чем реализацию, таким образом, вы делаете свой код более гибким. Вы можете использовать другую реализацию List, которая выполняется быстрее или делает что-то еще, без изменения кода клиента.
Ответ 2
Статик - это неправильная вещь, которую можно использовать здесь.
Концептуально статично неправильно, потому что это только для служб, которые не соответствуют реальному объекту, физическому или концептуальному. У вас есть несколько таблиц, и каждый из них должен быть представлен фактическим объектом в системе, а не просто классом. Это звучит немного теоретически, но, как мы увидим, оно имеет реальные последствия.
Каждая таблица имеет другой класс, и это нормально. Поскольку у вас может быть только одна из каждой таблицы, ограничьте количество экземпляров каждого класса одним (используйте флаг - не делайте его Singleton). Заставьте программу создать экземпляр класса, прежде чем он обратится к таблице.
Теперь у вас есть несколько преимуществ. Вы можете использовать всю силу наследования и переопределять, поскольку ваши методы больше не являются статическими. Вы можете использовать конструктор для любой инициализации, включая сопоставление SQL с таблицей (SQL, которую ваши методы могут использовать позже). Это должно привести к тому, что все ваши проблемы выше уходят или, по крайней мере, станут намного проще.
Кажется, что есть дополнительная работа по созданию объекта и дополнительной памяти, но это действительно тривиально по сравнению с преимуществами. Несколько байтов памяти для объекта не будут замечены, а несколько вызовов конструктора могут понадобиться, возможно, через десять минут. Против этого есть преимущество в том, что код для инициализации любых таблиц не нужно запускать, если таблица не используется (конструктор не следует вызывать). Вы найдете, что это упрощает многое.
Ответ 3
Почему бы не использовать аннотации? Они хорошо подходят, что вы делаете: добавить мета-информацию (здесь SQL-запрос) в класс.
Ответ 4
Как и было предложено, вы можете использовать аннотации или переместить статические методы в объекты factory:
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....."; }
}
Но это не очень хороший запах, чтобы иметь объекты без какого-либо состояния.
Ответ 5
Если вы передаете класс для findAll, почему вы не можете передать класс getSelectSQL в ModelBase?
Ответ 6
asterite: вы имеете в виду, что getSelectSQL существует только в ModelBase, и он использует переданный в классе, чтобы сделать tablename или что-то в этом роде?
Я не могу этого сделать, потому что некоторые из моделей имеют дикие отличия от отдельных конструкций, поэтому я не могу использовать универсальный "select * from" + classToTableName();. И любая попытка получить информацию от моделей об их конструкции выбора сталкивается с той же проблемой из первоначального вопроса - вам нужен экземпляр Модели или какое-то причудливое отражение.
gizmo: Я обязательно посмотрю аннотации. Хотя я не могу помочь, но задаюсь вопросом, что люди делали с этими проблемами до того, как появились размышления?
Ответ 7
У вас могут быть ваши методы SQL как методы экземпляра в отдельном классе.
Затем передайте объект модели в конструктор этого нового класса и вызовите его методы для получения SQL.
Ответ 8
Wow - это намного лучший пример того, что я задал ранее в более общих терминах - как реализовать свойства или методы, которые являются Static для каждого класса реализации таким образом, который позволяет избежать дублирования, обеспечивает статический доступ без необходимости создания экземпляра класса и чувствует "право".
Короткий ответ (Java или .NET): Вы не можете.
Более длинный ответ - вы можете, если не возражаете использовать аннотацию уровня класса (отражение) или создание экземпляра объекта (метод экземпляра), но ни один из них не является действительно "чистым".
См. мой предыдущий (связанный) вопрос здесь: Как обрабатывать статические поля, которые различаются в зависимости от класса реализации
Я думал, что все ответы были очень слабыми и пропустили суть. Ваш вопрос намного лучше сформулирован.
Ответ 9
Я согласен с Gizmo: вы либо смотрите аннотации, либо какой-то конфигурационный файл. Я бы посмотрел на Hibernate и другие структуры ORM (и, возможно, даже библиотеки, такие как log4j!), Чтобы посмотреть, как они обрабатывают загрузку метаинформации на уровне класса.
Не все может или должно быть сделано программно, я считаю, что это может быть один из этих случаев.