存储库模式是否遵循SOLID原则?

时间:2015-01-23 04:17:43

标签: c# asp.net asp.net-mvc design-patterns solid-principles

我正在对SOLID主体进行一些研究,并在Repository模式的实现中发现了一些问题。我将解释每一个问题,如果我错了,请纠正我。

问题1

存储库模式违反单一责任原则( S

假设我们有一个定义为

的界面
public interface IRepository<T> where T: IEntity
{ 
    IEnumerable<T> List { get; }
    void Add(T entity);
    void Delete(T entity);
    void Update(T entity);
    T FindById(int Id);
}

显然它违反了单一责任原则,因为当我们实现这个接口时,在单个类中我们都放置了Command和Query。这不是预期的。

问题2

存储库模式中断接口隔离原则( I

说我们有2个以上接口的实现。

首次实施

CustomerRepository : IRepository<Customer>
{
   //All Implementation
}

第二次实施

ProductRepository : IRepository<Product>
{
   //All Implementation except Delete Method. So Delete Method Will be
   void Delete (Product product){
       throw Not Implement Exception!
   }
}

根据ISP“没有客户应该被迫依赖它不使用的方法。”所以我们看到它显然也违反了ISP。

所以,我的理解是Repository模式不遵循SOLID主体。你怎么看?我们为什么要选择这种违反校长的模式呢?需要你的意见。

6 个答案:

答案 0 :(得分:24)

  

显然它违反了单一责任原则,因为当我们实现这个接口时,在单个类中我们都放置了Command和Query。这不是预期的。

这不是单一责任原则的含义。 SRP意味着该类应该具有一个主要关注点。存储库的主要关注点是“使用类似于集合的接口来访问域对象,在域和数据映射层之间进行调解”({{3 }})。这就是这门课的作用。

  

存储库模式中断接口隔离原则

如果这让您感到烦恼,那么只需提供另一个不包含您不会实施的方法的界面。不过,我个人不会这样做;它为边际收益提供了许多额外的接口,并且不必要地使API混乱。 NotImplementedException 非常不言自明。

你会发现计算中存在许多有例外的规则,法律或原则,有些是完全错误的。接受歧义,学习从更实际的角度编写软件,并停止以这样的绝对术语思考软件设计。

答案 1 :(得分:5)

  

显然它违反了单一责任原则

只有你对SRP的含义有一个非常狭隘的定义,这一点很清楚。事实是SOLID违反了SOLID。这些原则本身就是矛盾的。 SRP与DRY不一致,因为您经常需要重复自己以正确分离问题。在某些情况下,LSP与ISP不一致。 OCP经常与DRY和SRP发生冲突。这些原则并不是那么严格和快速的规则,而是指导你......尽量遵守它们,但不要将它们视为不能破坏的法则。

最重要的是,您将使用非常具体的通用存储库实现模式来混淆Repository体系结构模式。请注意,通用存储库与具体存储库不同。也没有要求Repository实现您提到的方法。

是的,您可以将命令和查询分开作为两个单独的问题,但不要求您这样做以使每个人都承担一项责任。命令查询分离是一个很好的原则,但不是SOLID所涵盖的内容,当然也没有就是否将问题分离出不同责任的问题达成共识。他们更像是同一责任的不同方面。如果您想要并且声称更新与删除不同,或者通过ID查询与按类型或其他方式查询不同,您可以将此设置为荒谬的级别。在某些时候,你必须画线和盒子,对大多数人来说,“阅读和写作一个实体”是一个单一的责任。

  

存储库模式中断接口隔离原则

首先,您将Liskov Substitution Principal与接口隔离原则混淆。 LSP是你的例子违反的。

正如我之前所说,除了“类似集合的接口”之外,不要求Repository实现任何特定的方法集。事实上,像这样实现它是完全可以接受的:

public interface IRepository<T> where...[...] {IEnumerable<T> List { get; }}
public interface CustRepository : IRepository<Customer>, IRepoAdd, IRepoUpdate, IRepoDelete, IRepoFind {}

现在它可以选择实现任何其他成员而不破坏LSP,虽然它是一个相当愚蠢的实现,我当然不会只是为了避免破坏LSP而实现。

事实上,您可能没有充分的理由想要一个没有删除的存储库。我能想到的唯一可能的原因是只读存储库,我将为使用只读集合接口定义一个单独的接口。

答案 2 :(得分:3)

我自己使用Repository模式,并使用该模式确保实现所有必需的接口。为此,我为所有操作(IEntityCreator,IEntityReader,IEntityUpdater,IEntityRemover)创建了单独的接口,并使repostiory继承了所有这些接口。这样我就可以在具体类中实现所有方法,并且仍然分别使用所有接口。我没有理由说明存储库模式违反了SOLID原则。您只需要正确定义存储库的“责任”:存储库的职责是促进对类型T数据的所有访问。这就是所有要说的。如上所述,我还有一个名为ReferenceRepository<T>的只读存储库接口,它只包含IEntityReader<T>接口。下面定义了所有接口以便快速复制:)最重要的是,我还创建了一些具体的类,包括缓存和/或日志记录。这将包含ISOLID所述的任何进一步操作。类型IEntity用作标记接口,仅允许实体而不允许其他类型的对象(您必须从某处开始)。

/// <summary>
/// This interface defines all properties and methods common to all Entity Creators.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface IEntityCreator<TEntity>
    where TEntity : IEntity
{
    #region Methods
    /// <summary>
    /// Create a new instance of <see cref="TEntity"/>
    /// </summary>
    /// <returns></returns>
    TEntity Create();
    #endregion
}

/// <summary>
/// This interface defines all properties and methods common to all Entity Readers.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface IEntityReader<TEntity>
   where TEntity : IEntity
{
    #region Methods
    /// <summary>
    /// Get all entities in the data store.
    /// </summary>
    /// <returns></returns>
    IEnumerable<TEntity> GetAll();

    /// <summary>
    /// Find all entities that match the expression
    /// </summary>
    /// <param name="whereExpression">exprssion used to filter the results.</param>
    /// <returns></returns>
    IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> whereExpression);
    #endregion
}

/// <summary>
/// This interface defines all properties and methods common to all Entity Updaters. 
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface IEntityUpdater<TEntity>
    where TEntity : IEntity
{
    #region Methods
    /// <summary>
    /// Save an entity in the data store
    /// </summary>
    /// <param name="entity">The entity to save</param>
    void Save(TEntity entity);
    #endregion
}

/// <summary>
/// This interface defines all properties and methods common to all Entity removers.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface IEntityRemover<TEntity>
    where TEntity : IEntity
{
    /// <summary>
    /// Delete an entity from the data store.
    /// </summary>
    /// <param name="entity">The entity to delete</param>
    void Delete(TEntity entity);

    /// <summary>
    /// Deletes all entities that match the specified where expression.
    /// </summary>
    /// <param name="whereExpression">The where expression.</param>
    void Delete(Expression<Func<TEntity, bool>> whereExpression);
}

/// <summary>
/// This interface defines all properties and methods common to all Repositories.
/// </summary>
public interface IRepository { }

/// <summary>
/// This interface defines all properties and methods common to all Read-Only repositories.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface IReferenceRepository<TEntity> : IRepository, IEntityReader<TEntity>
   where TEntity : IEntity
{

}

/// <summary>
/// This interface defines all properties and methods common to all Read-Write Repositories.
/// </summary>
public interface IRepository<TEntity> : IReferenceRepository<TEntity>, IEntityCreator<TEntity>,
    IEntityUpdater<TEntity>, IEntityRemover<TEntity>
    where TEntity : IEntity
{

}

答案 3 :(得分:1)

我认为它确实破坏了ISP。就是这样。

也许这是一个既定的模式,人们很难接受。

https://www.newyorker.com/magazine/2017/02/27/why-facts-dont-change-our-minds

  

接口隔离原则(ISP)指出,不应强迫任何客户端依赖其不使用的方法。[1] ISP将非常大的接口拆分为更小和更具体的接口,以便客户端只需要了解他们感兴趣的方法即可。

我正在实现API资源以获取订单。对于典型的存储库,我不得不依赖可以更新和删除内容的大型存储库。

我宁愿只依赖并嘲笑或伪造这种仅能获得订单的类型。

答案 4 :(得分:0)

我知道这是一条旧帖子,只想提供2美分。如果您想更好地遵循Solid,则需要将界面分为不同的版本。一种用于读取,一种用于编辑,删除等,用于接口隔离。但是理想情况下,我不会使用存储库模式,而是希望使用其自己的接口为每个实体或目的创建一个存储库。

答案 5 :(得分:0)

是的,这很古老,但是我认为其中一部分尚不清楚。 我同意提议的存储库模式显然会破坏SRP,但是这取决于SRP的定义

关于某物仅应具有“ 单一责任”的定义是含糊的,因为在这种情况下,您总是可以辩称某物仅具有一种责任。是的,请确保存储库仅在您的应用程序和数据库之间进行中介。但是更深一层,它负责读取,写入,更新,删除...

我实施的最后一个CLI只是一个责任...处理与某些API的通信...但是更深一层...您明白了

我更喜欢罗伯特·C·马丁(Robert C. Martin)的定义,该定义指出SRP的意思是:“ 只有一个改变的理由。”在我看来,这是更为精确的。如果写入/更新发生更改(审核),读取发生更改(缓存)或删除发生更改(实际删除之前的挑战)等等,则存储库可能会发生更改。

接着,对于每个CRUD操作,建议的具有单个接口的答案将遵循SRP并且也遵循ISP,因为两者基本上彼此相关。