仓储模式实现

17

我发现每次在使用仓储模式时,不同的实现方式都有所不同。以下是我主要发现的两个例子。

interface IProductRepository
{
    IQueryable<Product> FindAll();
}

通常还有另一层与存储库进行交互,调用FindAll()方法并执行任何操作,例如查找以字母's'开头的产品或获取特定类别中的产品。

我经常发现另一个例子将所有的查找方法放在存储库中。

interface IProductRepository
{
    IEnumerable<Product> GetProductsInCategory(int categoryId);
    IEnumerable<Product> GetProductsStartingWith(string letter);
    IEnumerable<PromoCode> GetProductPromoCodes(int productId);
}

您推荐我采取哪种路径?或者说各个路径的优缺点是什么?

在阅读http://martinfowler.com/eaaCatalog/repository.html之后,据我的理解,第一种方法似乎最能反映出这一点?

5个回答

16
第一种方法很糟糕。IQueryable就像是一个全能对象。即使在所有ORM中,找到一个100%完整的实现也非常困难。否则你可能会得到泄漏的抽象层,所以你可以直接公开ORM而不是使用它。
Joel在他的文章中说得最好(文字来自维基百科文章):

在Spolsky的文章中,他提到了许多抽象的例子,这些抽象大多数情况下都有效,但其中底层复杂性的一个细节无法忽略,因此将复杂性带入了原本应该通过抽象本身得到简化的软件中

Joels博客文章 第二种方法更容易实现并保持抽象完整。 更新 由于您的存储库有两个更改的原因,因此违反了单一职责原则。一个原因是如果产品API发生更改,另一个原因是如果PromoCode API发生更改。您应该使用两个不同的存储库,例如:
interface IProductRepository
{
    IEnumerable<Product> FindForCategory(int categoryId);
    IEnumerable<Product> FindAllStartingWith(string letter);
}

interface IPromoCodeRepository
{
    IEnumerable<PromoCode> FindForProduct(int productId);
}

改动如下:

  • 当一个方法返回多个项时,我倾向于以Find开头,如果只返回一个项,则以Get开头。
  • 较短的方法名称=更易读。
  • 单一职责。这样可以更容易地了解使用存储库的类对依赖项的需求。

小而明确的接口使得更容易发现违反SOLID原则的情况,因为违反原则的类往往会有臃肿的构造函数。


谢谢您的回复。我以为应该按聚合来分组?每个实体应该有一个仓储库吗?这里还有我提供的第一个示例的例子:https://dev59.com/um445IYBdhLWcg3wE2Tw - Scott
每个聚合。我无法确定促销代码仅适用于产品(因为它被命名为“GetProductPromos”而不仅仅是“GetPromos”)。 - jgauffin
没有你的第一个接口,还有改变的两个原因?第一个是将FindForCategory方法更改为在没有通过categoryId传递任何产品时更改默认返回的产品,并且第二个是更改FindAllStartingWith以应用一些默认过滤器? - Masoud
不。责任(以及更改的原因)是为了抽象出产品数据源处理。如果该处理发生更改,则类别更改。 - jgauffin

1

越来越多的人开始认同第二个选项。除了IQueryable在查询逻辑上泄漏到各个地方之外,正确实现它的难度很大,而且测试和模拟也非常困难。


0
个人建议使用第二个例子,这样您可以将搜索逻辑封装在一个地方,并且调用者的意图可以通过他们所调用的方法的名称清晰地定义。如果您选择第一个示例,则查询代码将遍布整个应用程序,您最终会重复查询。

0

我认为第一个更好。我的决定基于以下因素:

  1. 如果产品结构将被重构:

    • IQueryable方法 - 你只需要改变代码中的调用方法。
    • IEnumerables方法 - 你需要同时更改方法名称。

  2. 如果有很多要派生的接口,你想以多态的方式迭代:

    • IQueryable方法 - 泛型统一方法名称的好处
    • IEnumerables方法 - 有些名称可能不描述你需要的方法。

  3. 弹性

    • IQueryable方法 - 容易分成IEnumerables方法。
    • IEnumerables方法 - 难以转换回IQueryable方法。

所以,我建议你从IQueryable作为默认选择开始,随着你的代码进展,你总是可以改变到更具体的IEnumerables方法。


0

我建议避免重复。那是第一个目标。如果你有一些逻辑在多个地方查找以某个字母开头的产品,那么这是一个特殊情况,值得提取到单独的方法中(同时它可以为您的特定情况提供很好的描述)。没有重复代码更容易改变、理解和维护。

因此,我倾向于有一个通用的搜索方法使用 IQueryable 和一组被多次使用的方法:

interface IRepository<T>
{
    IQueryable<T> FindAll();
}

interface IProductRepository : IRepository<Product>
{
    IEnumerable<Product> GetProductsInCategory(int categoryId);
    IEnumerable<Product> GetProductsStartingWith(string letter);
    IEnumerable<PromoCode> GetProductPromoCodes(int productId);
}

同时考虑单元测试。具体方法比IQueryable更容易模拟。


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