Repository模式的实现
Repository模式的实现
似乎每个我找到的仓储模式示例,其实现都有一些不同之处。以下是我主要找到的两个示例。
接口 IProductRepository { IQueryableFindAll(); }
然后通常还有另一个层次与仓储进行交互,并调用 FindAll() 方法,执行诸如查找以字母's'开头的产品或获取特定类别的产品等操作。
我还经常找到另一种示例,将所有的查找方法都放入仓储中。
接口 IProductRepository { IEnumerableGetProductsInCategory(int categoryId); IEnumerable GetProductsStartingWith(string letter); IEnumerable GetProductPromoCodes(int productId); }
你推荐我选择哪种方式?或者说它们各自的优劣势是什么?
根据我阅读的http://martinfowler.com/eaaCatalog/repository.html,第一种方法似乎最能准确地反映出仓储模式的特点。
问题的出现原因是:使用第一个示例会导致查询代码在整个应用程序中泄漏,从而导致查询的重复。
解决方法是:使用第二个示例,将搜索逻辑封装在一个单一的地方,并通过调用者调用的方法名称明确定义调用者的意图。
以下是整理成的文章:
在软件开发过程中,经常会遇到需要在数据库中执行查询操作的情况。在处理这些查询时,我们希望能够将查询逻辑封装在一个单一的地方,以便于管理和维护。然而,有时候我们可能会面临一些问题,比如查询代码泄漏和查询重复等。
为了解决这些问题,我个人建议使用"Repository Pattern Implementation"来管理查询操作。下面我将通过两个示例来说明这个问题的出现原因以及解决方法。
首先,我们来看第一个示例。在这个示例中,查询代码分散在整个应用程序中。这意味着每次需要执行查询操作时,我们都需要编写重复的查询代码。这样做不仅增加了代码的重复性,还使得查询逻辑变得难以管理和维护。当我们需要对查询逻辑进行修改时,需要在多个地方进行修改,这增加了出错的可能性。
相比之下,第二个示例使用了"Repository Pattern Implementation"。在这个示例中,我们将查询逻辑封装在一个单一的地方,即"Repository"类中。通过在"Repository"类中定义不同的查询方法,我们可以将查询逻辑抽象出来,并为调用者提供清晰明确的方法名称。这样一来,调用者只需要调用相应的方法即可完成所需的查询操作,而无需关心具体的查询实现细节。这不仅提高了代码的可读性和可维护性,还避免了查询代码的重复。
总结起来,使用"Repository Pattern Implementation"可以有效地解决查询代码泄漏和查询重复的问题。通过将查询逻辑封装在一个单一的地方,并为调用者提供清晰明确的方法名称,我们可以更好地管理和维护查询操作,提高代码的可读性和可维护性。因此,我建议在开发过程中使用"Repository Pattern Implementation"来处理查询操作。
Repository模式的实现问题
在实现Repository模式时,有一个问题逐渐引起了共识:第二个选项是最好的选择。除了使用IQueryable时查询逻辑散落各处外,正确实现这个模式也很困难,而且很难进行测试和模拟。
问题的出现原因:
1. 查询逻辑散落:使用IQueryable接口时,查询逻辑往往会散落在代码的各个地方,没有一个明确的中心化位置。这导致了代码的可读性和可维护性下降。
解决方法:
采用第二个选项,即将查询逻辑集中到Repository中进行处理。
代码示例:
public interface IRepository{ IQueryable Query(); // 其他Repository方法... } public class Repository : IRepository { private readonly DbContext _context; public Repository(DbContext context) { _context = context; } public IQueryable Query() { return _context.Set ().AsQueryable(); } // 其他Repository方法的实现... }
通过上述代码示例,我们可以将查询逻辑集中到Repository类中。通过实现IRepository接口,我们可以定义通用的Repository方法,并在具体的实现类中实现这些方法。
使用Repository模式的好处:
1. 查询逻辑集中:使用Repository模式,可以将所有的查询逻辑集中到Repository类中。这样,代码会更加清晰和易于维护。
2. 更好的测试和模拟:由于查询逻辑被封装在Repository类中,我们可以更轻松地对Repository类进行测试和模拟,而不需要关注具体的数据源。
通过将查询逻辑集中到Repository类中,我们可以解决使用IQueryable时查询逻辑散落的问题,并且可以更好地进行测试和模拟。使用Repository模式,我们能够提高代码的可读性和可维护性,使得代码更加清晰和易于理解。
Repository Pattern Implementation(仓库模式的实现)的问题出现的原因是现有的方法实现很糟糕。IQueryable就像是一个上帝对象(GOD object)一样,很难找到一个完美的实现(即使在所有的ORM中)。否则,你可能会得到一个泄漏的抽象层(leaky abstraction layer)。Joel在他的文章中说得最好(文本来自维基百科的文章):“在Spolsky的文章中,他提到了许多抽象的例子,这些抽象大多数情况下都是有效的,但是底层复杂性的一个细节不能被忽略,因此将复杂性传递到了本应该通过抽象来简化的软件中”。第二种方法更容易实现和保持抽象的完整性。
更新
你的仓库违反了单一职责原则,因为它有两个改变的原因。第一个原因是如果Products API被改变,另一个原因是如果PromoCode API被改变。你应该使用两个不同的仓库,如下所示:
interface IProductRepository { IEnumerableFindForCategory(int categoryId); IEnumerable FindAllStartingWith(string letter); } interface IPromoCodeRepository { IEnumerable FindForProduct(int productId); }
改变的内容:
- 当返回多个项时,我倾向于以Find开头的方法,如果返回单个项,则以Get开头。
- 方法名更短,更容易阅读。
- 单一职责。更容易知道使用仓库的类具有哪些依赖关系。
- 简洁明确的接口更容易发现违反SOLID原则的情况,因为违反原则的类往往会有臃肿的构造函数。
感谢您的回复。我以为你的意思是按聚合分组?我应该为每个实体拥有一个仓库吗?此外,这是我提供的第一个例子的示例。
按聚合分组。我无法确定促销码是否只适用于产品(因为它的名称是GetProductPromos而不仅仅是GetPromos)。
没有你提供的第一个接口,还有两个改变的原因?第一个:将FindForCategory方法更改为在找不到通过传递的categoryId的任何产品时更改默认返回的产品,第二个:更改FindAllStartingWith以应用一些默认过滤器吗?
不是的。改变的责任(和改变的原因)是将产品的数据源处理抽象化。如果处理方式发生改变,类也会发生改变。