似乎每个我找到的存储库模式的例子,实现在某种程度上都是不同的。以下是我主要找到的两个例子。
interface IProductRepository
{
IQueryable<Product> FindAll();
}
然后通常有另一个层与存储库通信并调用FindAll()方法并执行任何操作,例如查找以字母“s”开头的产品或获取特定类别的产品。
另一个例子我发现很多将所有的find方法放入存储库
interface IProductRepository
{
IEnumerable<Product> GetProductsInCategory(int categoryId);
IEnumerable<Product> GetProductsStartingWith(string letter);
IEnumerable<PromoCode> GetProductPromoCodes(int productId);
}
您建议我选择哪条路径?或者彼此的优点/缺点是什么?
根据我的理解,阅读http://martinfowler.com/eaaCatalog/repository.html第一种方法似乎最能反映这一点?
答案 0 :(得分:16)
第一个是可怕的。 IQueryable
就像GOD object。很难找到100%完整的实现(即使在所有OR / Ms中)。您可以直接公开ORM而不是使用它,因为否则您可能会获得leaky abstraction layer。
乔尔说得最好(文字来自wikipedia article):
在Spolsky的文章中,他提到了很多关于抽象的例子,这些抽象在大多数时候都有效,但是底层复杂性的细节不容忽视,从而将复杂性推向应该通过抽象简化的软件本身
第二种方法更容易实现并保持抽象不变。
<强>更新强>
您的存储库违反了单一责任原则,因为它有两个原因需要更改。第一个是如果更改Products 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原则的行为,因为类的中断原则往往会使得构造函数变得臃肿。
答案 1 :(得分:1)
共识正在建立:第二种选择。除了查询逻辑泄漏到IQueryable的所有地方,正确实现它的困难,TEST和mock非常困难。
答案 2 :(得分:0)
我个人建议使用第二个例子,这样你就可以将搜索逻辑封装在一个地方,并且调用者的意图由他们调用的方法的名称明确定义。如果您使用第一个示例,您的查询代码将在整个应用程序中泄漏,您将最终重复查询。
答案 3 :(得分: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相比,特定方法更易于模拟。
答案 4 :(得分:0)
我实际上认为第一个更好。我假设我决定以下因素:
如果产品结构将被重构:
如果有很多人想要以多态方式派生你想要迭代的接口:
Elasticness
因此,我建议您从IQueryable开始作为默认选择,并且随着代码的进展,您可以随时更改为您需要的更具体的IEnumerables方法。