Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top