DAO模式和开闭原则

10
我看过并处理了很多基于JDBC的旧DAO代码,通常都是以CRUD方法开始。我的问题特别关注检索方法或“查找器”。通常我发现DAO从两个方法开始:
  • 查找并返回全部
  • 根据唯一标识符检索特定实例
往往情况下,这两种查找器是不够用的。我经常看到DAO类反复修改以添加以下查找器方法:
  • 查找并返回满足{条件}的所有内容
结果当需要支持新的{条件}或对现有方法进行修改以添加作为标志的新参数以修改方法内的SQL查询以支持附加条件时,就会添加更多方法。
这是一种丑陋的方法,违反了开放封闭原则。每当需要支持某个新的检索条件时,DAO类不断修改,一直都让我不爽。研究这个问题时,常常会指向存储库模式并将检索条件封装为规范或查询对象,然后将它们传递给查找器方法。但如果您拥有整个数据集的内存集合或者使用某种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();
2个回答

4
我们在数据层ORM中使用了iBatis,并且通过传递参数对象实现了您所提出的内容,只需一个查询即可。然后,在WHERE子句中,您可以将每个字段指定为条件子句,但仅当它在参数对象中填充时才能指定。如果参数对象中只有一个字段不为空,则只使用该字段来过滤结果。
因此,如果您需要向参数中添加字段,则只需更改SQL和paramObj即可。然后,您可以拥有两种基于传递参数组合返回全部或子集的方法,或者至少这种方法可以减少所需的查询数量。
例如,类似于以下内容...
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
+1 我在 iBatis 中也做了同样的事情,支持可选日期范围、可选的 LIKE 谓词等等。它运行良好。(顺便说一句,在你的示例中不需要主键条件。) - Jim Ferrans
我猜这在相对简单的条件子句中可能有效。但如果条件变得更加复杂,或者某些检索需要依赖于另一个表的连接时,扩展性将会很困难。例如,我可以有一个CustomerDAO,它只根据姓名检索客户。但是,如果我还想根据未结余额检索客户,这将需要从另一个表获取信息。我最终会在我的DAO中拥有一个复杂的查询构建器,以预先处理各种不同的参数。 - user620884
@eplozada 确实是这样,你需要更多的选项。你的问题最初提到了简单的CRUD,以及它如何扩展。你可以针对你的联接表示例拥有一个单独的查找方法,并在其中设置参数。如果你正在寻找一种方法调用,可以满足你可能想象的所有查询客户表和相关联表的场景,我认为它可能变得非常复杂。在DAO中使用分离的方法比那更可取。对于更复杂的条件,方法名称将向消费者指示查询类型。 - MadMurf
谢谢@Jim,这个错误让我很震惊,但是为了自己辩解一下,现在是悉尼的星期五下午,这周过得非常漫长。 - MadMurf
@Jim - 感谢你们迄今为止的回复。我通常会退而求其次,采用类似于你们的方法。但实际上,我最终只是不断向DAO添加更多的方法 :(. 哎,回到设计板上吧。(由于可怕的语法问题,我删除并替换了之前的评论) - user620884

0

不要针对每个可能出现的条件创建特定的查找方法,为什么不创建通用的查找API呢? 这可以采用具有内部枚举表示字段的DAO的形式,并且使用一个方法来接收DAO的内部类实例列表,该内部类具有表示要过滤哪个DAO字段,要在其上应用什么过滤器以及什么条件(AND、OR等)的字段。

这需要一些工作来设置,在小型项目中可能会过度杀伤力,但肯定是可行的。 ORM框架通常已经内置了类似的功能,因此您可能希望考虑采用其中之一(或至少在设计自己的解决方案以用于改装旧应用程序时查看它们的实现)。


当我实现规范模式时,我试图实现一个通用的查找器或可扩展的查找器。这已经接近我所寻求的目标,因为传递给单个查找器方法的SearchCriteria对象也是从领域角度实现的。我不希望SearchCrietria对象本身与特定实现绑定,因为这将首先违背DAO的目的。 - user620884
有了泛型,你可以创建一个SearchCriteria<DAOClass>类型的基础设施,在其中你可以有一个单独的标准类与每个DAO相关联,而不需要将DAO链接回标准类。 - jwenting

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接