Паттерн DAO и принцип открытого-закрытого

Я видел и работал со многими старыми кодами DAO на основе JDBC, которые обычно начинаются с методов CRUD. Мой вопрос относится конкретно к методам поиска или «искателям». Обычно я обнаруживаю, что DAO начинают с двух методов:

  • найти и вернуть ВСЕ
  • получить конкретный экземпляр на основе уникального идентификатора

Чаще всего этих двух искателей недостаточно. Обычно я вижу, что класс DAO неоднократно модифицировался, чтобы добавить методы поиска, подобные следующим:

  • найти и вернуть ВСЕ, где {условие}

Что происходит, так это то, что добавляются дополнительные методы, когда необходимо поддерживать новые {условия} или существующие методы модифицируются для добавления новых параметров в качестве флагов для изменения SQL-запроса внутри метода для поддержки дополнительных условий.

Это уродливый подход, который нарушает принцип открытого-закрытого. Меня всегда раздражало, что классы DAO постоянно модифицируются всякий раз, когда необходимо поддерживать какое-то новое условие поиска. Исследования по этому вопросу часто указывают мне на шаблон репозитория и инкапсуляцию условий для извлечения в виде спецификаций или объектов запроса, а затем их передачу. методу поиска. Но это кажется возможным только в том случае, если у вас есть коллекция всего набора данных в памяти или если вы используете какой-то ORM (я работаю со старым кодом JDBC)

Я рассмотрел решение, которое лениво загружает весь набор данных, которым управляет DAO, как коллекцию в память, а затем использует шаблон спецификации в качестве запросов для извлечения. Затем я реализую какой-то наблюдатель в коллекции, который просто обновляет базу данных при вызове методов создания, обновления или удаления. Но очевидно, что производительность и масштабируемость значительно страдают.

Есть мысли по этому поводу?


Спасибо за ответы. У меня есть одна мысль: каково ваше мнение об использовании шаблона Command/Policy для инкапсуляции запросов на доступ к данным? Каждая отдельная конкретная команда может представлять определенный вид доступа и может быть передана Invoker. Я бы закончил с многочисленными классами Concrete Command, но каждый из них будет ориентирован только на один вид доступа и должен быть хорошо тестируемым и изолированным.

    public abstract class Command<R>{
       public <R> execute();
       public void setArguments(CommandArguments args){
          //store arguments  
       }
    }

    //map based structure for storing and returning arguments
    public class CommandArguments{
         public String getAsString(String key);
         public String getAsInt(String key);
         //... others
    }

    //In some business class...
    Command command = CommandFactory.create("SearchByName");
    CommandArguments args = new CommandArguments();
    args.setValue("name", name);
    // others
    command.setArguments(args);
    List<Customer> list  = command.execute();

person Community    schedule 17.02.2011    source источник


Ответы (2)


Мы использовали iBatis для нашего ORM уровня данных и смогли реализовать то, что вы предлагаете, в одном запросе, передав объект параметра с различными полями, которые вы, возможно, захотите использовать в качестве параметров.

Затем в вашем предложении WHERE вы можете указать каждое поле как условие условия, но только если оно заполнено в объекте параметра. Если только одно поле в параметре obj не равно нулю, то только оно будет использоваться для фильтрации результатов.

Таким образом, если вам нужно добавить поля к своим параметрам, вы просто меняете SQL и paramObj. Затем у вас может быть 2 метода, которые возвращают ВСЕ или подмножество на основе переданной комбинации параметров, или, по крайней мере, этот подход уменьшит количество необходимых запросов.

например что-то в духе...

SELECT * FROM MY_TABLE
WHERE FIELD_ZERO = paramObj.field0
<isNotNull property="paramObj.field1">AND FIELD_ONE = paramObj.field1</isNotNull>
<isNotNull property="paramObj.field2">AND FIELD_TWO = paramObj.field2</isNotNull>
<isNotNull property="paramObj.field3">AND FIELD_THREE = paramObj.field3</isNotNull>
person MadMurf    schedule 18.02.2011
comment
+1 Я сделал то же самое в iBatis, поддерживая необязательные диапазоны дат, необязательные предикаты LIKE и так далее. Это работает хорошо. (Кстати, вам не нужно условие первичного ключа в вашем примере.) - person Jim Ferrans; 18.02.2011
comment
Я предполагаю, что это могло бы работать в относительно более простых пунктах where. Но было бы сложно масштабировать, если бы условия стали более сложными или когда некоторые выборки зависели бы от соединений из другой таблицы. Например, у меня может быть CustomerDAO, который просто извлекает клиентов на основе имени. Но что, если я также хочу получить клиента на основе непогашенных остатков, для которых потребуется информация из другой таблицы. В конечном итоге у меня будет сложный построитель запросов внутри моих DAO только для того, чтобы предвидеть различные параметры, которые можно передать. - person ; 18.02.2011
comment
@eplozada правда, тогда вам понадобится больше вариантов, ваш вопрос начинается со ссылки на простой CRUD и на то, как он расширяется. У вас может быть отдельный метод поиска для вашего примера таблицы соединения, а затем также параметры для него. Если вы ищете один вызов метода, который удовлетворит все возможные сценарии, которые вы могли бы предусмотреть для запроса вашей таблицы Customer вместе со связанными таблицами, я думаю, что это может стать ужасно сложным. Отдельные методы в вашем DAO предпочтительнее, чем это ИМХО. Для более сложных условий имя метода будет указывать тип запроса к потребителю. - person MadMurf; 18.02.2011
comment
Спасибо, @Jim, шокирующая ошибка, но в мою защиту сегодня пятница здесь, в Сиднее, и это была очень длинная неделя. - person MadMurf; 18.02.2011
comment
и @Jim - Спасибо за ответы. Обычно я возвращаюсь к чему-то похожему на то, что делаете вы, ребята. Но на самом деле я просто добавляю больше методов в DAO :(. Ну, вернемся к доске дизайна. (Удален и заменен мой предыдущий комментарий из-за ужасных грамматических проблем) - person ; 18.02.2011

Вместо того, чтобы создавать специальный метод поиска для каждого возможного условия, когда они становятся очевидными, почему бы не создать универсальный API поиска? Это может быть форма DAO, имеющая внутреннее Enum для представления полей, и метод, который принимает список экземпляров внутреннего класса DAO с полями, представляющими, какое поле DAO фильтровать, какой фильтр применять к нему, и какое условие (И, ИЛИ и т.д.).

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

person jwenting    schedule 17.02.2011
comment
Универсальный искатель или, скорее, расширяемый искатель — это то, чего я пытался достичь, когда реализовывал шаблон спецификации. Это было близко к тому, что я искал, поскольку объекты SearchCriteria, передаваемые в методе единственного поиска, также были реализованы с точки зрения предметной области. Я не хотел, чтобы сами объекты SearchCrietria были привязаны к конкретной реализации, поскольку это в первую очередь противоречило бы цели DAO. - person ; 17.02.2011
comment
с дженериками вы можете создать инфраструктуру типа SearchCriteria‹DAOClass›, в которой у вас есть отдельный класс критериев, связанный с каждым DAO, без привязки DAO обратно к классу критериев. - person jwenting; 17.02.2011