接口隔离和单一责任原则的问题

Mik*_*son 6 c# interface single-responsibility-principle solid-principles interface-segregation-principle

我试图遵循接口隔离单一职责原则,但是我对如何将它们整合在一起感到困惑。

在这里,我有一个示例,其中有一些接口,我已将其拆分为更小、更直接的接口:

public interface IDataRead
{
    TModel Get<TModel>(int id);
}

public interface IDataWrite
{
    void Save<TModel>(TModel model);
}

public interface IDataDelete
{        
    void Delete<TModel>(int id);
    void Delete<TModel>(TModel model);
}
Run Code Online (Sandbox Code Playgroud)

我稍微简化了它(有一些where条款妨碍了可读性)。

目前我正在使用SQLite ,但是,这种模式的美妙之处在于,如果我选择不同的数据存储方法(例如Azure),它有望让我有机会更好地适应变化。

现在,我对每个接口都有一个实现,下面是每个接口的简化示例:

public class DataDeleterSQLite : IDataDelete
{
    SQLiteConnection _Connection;

    public DataDeleterSQLite(SQLiteConnection connection) { ... }

    public void Delete<TModel>(TModel model) { ... }
}

... 

public class DataReaderSQLite : IDataRead
{
    SQLiteConnection _Connection;

    public DataReaderSQLite(SQLiteConnection connection) { ... }

    public TModel Get<TModel>(int id) { ... }
}

// You get the idea.
Run Code Online (Sandbox Code Playgroud)

现在,我在将它们整合在一起时遇到了问题,我确信总体思路是创建一个Database使用接口而不是类(真正的实现)的类。所以,我想出了这样的事情:

public class Database
{
    IDataDelete _Deleter;
    ...

    //Injecting the interfaces to make use of Dependency Injection.
    public Database(IDataRead reader, IDataWrite writer, IDataDelete deleter) { ... }
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是我应该如何向客户端公开IDataReadIDataWrite、 和接口?IDataDelete我应该重写方法来重定向到接口吗?像这样:

//This feels like I'm just repeating a load of work.
public void Delete<TModel>(TModel model)
{
    _Deleter.Delete<TModel>(model);
}
Run Code Online (Sandbox Code Playgroud)

强调我的评论,这看起来有点愚蠢,我费了很大的劲才将这些类分成漂亮的、独立的实现,现在我将它们全部重新组合到一个大型类中。

我可以将接口公开为属性,如下所示:

public IDataDelete Deleter { get; private set; }
Run Code Online (Sandbox Code Playgroud)

这感觉好一点,但是,客户端不应该经历决定他们需要使用哪个接口的麻烦。

我完全没有抓住要点吗?帮助!

Big*_*ddy 5

我完全没有抓住要点吗?帮助!

我不认为你完全错过了它,你走在正确的轨道上,但在这种情况下走得太远了。您的所有 CRUD 功能都完全相互关联,因此它们属于公开单一职责的单一接口。在我看来,如果您的接口暴露了 CRUD 函数和其他一些职责,那么重构为单独的接口将是一个很好的选择。

如果,作为你的功能的使用者,我必须实例化不同的插入、删除等类,我会来找你。


Arg*_*a C 3

当我们谈论接口隔离(甚至是单一责任)时,我们谈论的是创建执行一组逻辑上相关的操作并组合在一起形成有意义的完整实体的实体。

这个想法是,一个类应该能够从数据库读取实体,并用新值更新它。但是,一个类不应该能够获取罗马的天气并更新纽约证券交易所的股票价值!

为读、写、删除创建单独的接口有点极端。ISP 并没有严格规定在接口中只进行一项操作的规则。理想情况下,一个可以读、写、删除的接口构成一个完整的(但不因不相关的操作而变得庞大)接口。这里,接口中的操作不应该related互相dependent影响。

所以,按照惯例,你可以有一个像

interface IRepository<T>
{
    IEnumerable<T> Read();
    T Read(int id);
    IEnumerable<T> Query(Func<T, bool> predicate);
    bool Save(T data);
    bool Delete(T data);
    bool Delete(int id);
}
Run Code Online (Sandbox Code Playgroud)

您可以将此接口传递给客户端代码,这对他们来说完全有意义。它可以与遵循一组基本规则的任何类型的实体一起工作(例如,每个实体应由整数 id 唯一标识)。

另外,如果您的业务/应用程序层类仅依赖于此接口,而不是实际的实现类,如下所示

class EmployeeService
{
    readonly IRepository<Employee> _employeeRepo;

    Employee GetEmployeeById(int id)
    {
        return _employeeRepo.Read(id);
    }

    //other CRUD operation on employee
}
Run Code Online (Sandbox Code Playgroud)

然后,您的业务/应用程序类将完全独立于数据存储基础设施。您可以灵活地选择您喜欢的数据存储,然后只需使用此接口的实现将它们插入到代码库中即可。

您可以根据需要拥有OracleRepository : IRepository和/或MongoRepository : IRepository注射正确的药物。IoC

  • 那么系统中使用此“IRepository&lt;T&gt;”接口的每个类都使用其所有方法(示例中的所有六个方法)?实际上我从未在任何系统中见过这种情况发生,我敢打赌这不会在您的系统中发生。有些类只会使用“Delete”,而其他类只会调用“Read”。正如您所说,“可能根本不需要Delete()”。这本质上是一种 ISP 违规,因为它声明“任何客户端都不应被迫依赖于它不使用的方法”。违反 ISP 可能并不总是一个问题,但了解这一点很重要。 (2认同)