DAO 模式和开闭原则

Posted

技术标签:

【中文标题】DAO 模式和开闭原则【英文标题】:DAO pattern and the Open-Closed Principle 【发布时间】:2011-06-28 21:52:36 【问题描述】:

我见过并使用过很多旧的、基于 JDBC 的 DAO 代码,这些代码通常以 CRUD 方法开始。我的问题特别与检索方法或“发现者”有关。通常,我发现 DAO 以两种方法开始:

查找并返回所有 根据唯一标识符检索特定实例

通常,这两个查找器是不够的。我通常最终会看到一个 DAO 类被反复修改以添加如下查找器方法:

查找并返回 ALL where condition

当需要支持新的 conditions 或修改现有方法以添加新参数作为标志以修改方法内部的 SQL 查询以支持附加条件时,会发生什么情况。

这是一种丑陋的方法,违反了开闭原则。每当需要支持一些新的检索条件时,看到 DAO 类不断修改,这一直是我的烦恼。对这个问题的研究经常将我指向存储库模式并将检索条件封装为Specifications 或查询对象,然后将它们传递给查找器方法。但这似乎只有在您拥有整个数据集的内存集合或者您使用某种 ORM(我正在使用较旧的 JDBC 代码)时才可行

我考虑过一种解决方案,即延迟加载 DAO 管理的整个数据集作为内存中的集合,然后使用规范模式作为检索查询。然后我在集合上实现了某种观察者,它只在调用创建、更新或删除方法时更新数据库。但显然性能和可扩展性会受到严重影响。

对此有什么想法吗?


感谢您迄今为止的回复。我确实有一个想法——您对使用命令/策略模式封装数据访问请求有何看法?每个单独的具体命令可以代表一种特定类型的访问,并且可以传递给调用者。我最终会得到许多具体命令类,但每个类都将只关注一种访问,并且应该非常可测试和隔离。

    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();

【问题讨论】:

【参考方案1】:

我们已将 iBatis 用于我们的数据层 ORM,并且能够通过传递带有您可能希望用作参数的各种字段的参数对象来实现您在一个查询中提出的建议。

然后在 WHERE 子句中,您可以将每个字段指定为条件子句,但前提是其填充在参数对象中。如果参数 obj 中只有一个字段不为空,则它是唯一用于过滤结果的字段。

因此,如果您需要向参数添加字段,您只需更改 SQL 和 paramObj。然后,您可以有 2 个方法根据传递的参数组合返回 ALL 或子集,或者至少这种方法会减少所需的查询数量。

例如类似于...的东西

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>

【讨论】:

+1 我在 iBatis 中做过同样的事情,支持可选的日期范围、可选的 LIKE 谓词等等。它运作良好。 (顺便说一句,您的示例中不需要主键条件。) 我想这可以在相对简单的 where 子句中工作。但是,如果条件变得更加复杂或者某些检索将依赖于来自另一个表的连接时,将很难进行扩展。例如,我可以有一个 CustomerDAO,它只根据名称检索客户。但是,如果我还想根据需要从另一个表中获取信息的未结余额来检索客户,该怎么办。我最终会在我的 DAO 中拥有一个复杂的查询构建器,只是为了预测可以传递的各种不同参数。 @eplozada true 那么您需要更多选项,您的问题开始于引用简单的 CRUD 以及如何扩展。您可以为连接表示例使用单独的查找器方法,然后也可以使用参数。如果您正在寻找一种方法调用来满足您可能设想的查询客户表和链接表的所有可能场景,我认为它可能会变得非常复杂。您的 DAO 中的单独方法比恕我直言更可取。对于更复杂的条件,方法名称将向消费者指示查询的类型 谢谢@Jim,这是一个令人震惊的错误,但在我看来,这是悉尼的星期五,这是一个非常漫长的一周。 和 @Jim - 感谢您到目前为止的回复。我通常会回到与你们所做的类似的事情上。但实际上,我最终只是在 DAO 中添加了更多方法 :(。嗯,回到设计板。(由于可怕的语法问题,删除并替换了我之前的评论)【参考方案2】:

与其为每个明显的可能条件创建一个特定的查找器方法,不如创建一个通用的查找器 API? 这可以采用具有内部枚举来表示字段的 DAO 的形式,以及采用 DAO 内部类的实例列表的方法,其中字段表示要过滤的 DAO 的哪个字段,要对其应用什么过滤器,以及什么条件(AND、OR 等)。

设置起来有点麻烦,对于小型项目来说可能有点过头了,但它肯定是可行的。 ORM 框架通常已经内置了类似的东西,因此您可能需要考虑采用其中一种(或者至少在设计您自己的解决方案以改造您的遗留应用程序时,看看它们是如何实现它的)。

【讨论】:

一个通用的查找器或者更确切地说是一个可扩展的查找器是我在实现规范模式时想要实现的。这与我正在寻找的内容很接近,因为在单个 finder 方法中传递的 SearchCriteria 对象也是从域的角度实现的。我不希望 SearchCrietria 对象本身绑定到特定的实现,因为它首先会破坏 DAO 的目的。 使用泛型,您可以创建一个 SearchCriteria 类型的基础架构,其中您有一个单独的条件类链接到每个 DAO,而 DAO 不会链接回条件类。

以上是关于DAO 模式和开闭原则的主要内容,如果未能解决你的问题,请参考以下文章

抽象方法和开闭原则

抽象类和开闭原则

设计模式开闭原则

设计模式软件设计七大原则 ( 开闭原则 )

设计模式--6大原则--开闭原则

手撸golang 架构设计原则 开闭原则