C# · 3月 10, 2022

c# – 界面隔离和单一责任原则的困境

我正在尝试遵循接口隔离和单一责任原则,但是我对如何将它们整合在一起感到困惑.

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

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);}

我略微简化了(有一些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.

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

public class Database{ IDataDelete _Deleter; … //Injecting the interfaces to make use of Dependency Injection. public Database(IDataRead reader,IDataWrite writer,IDataDelete deleter) { … }}

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

//This feels like I’m just repeating a load of work.public void Delete<TModel>(TModel model){ _Deleter.Delete<TModel>(model);}

突出我的评论,这看起来有点愚蠢,我把很多问题分成很好的,分开的实现,现在我把它们全部重新组合在一个巨型课程中.

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

public IDataDelete Deleter { get; private set; }

这感觉好一点,然而,不应该期望客户必须经历决定他们需要使用哪个接口的麻烦.

我完全忽略了这一点吗?救命!

解决方法 当我们讨论界面隔离(甚至是单一责任)时,我们讨论的是让实体执行一组逻辑相关的操作,并将它们组合在一起形成一个有意义的完整实体.

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

为读,写,删除创建单独的接口有点极端.互联网服务提供商实际上没有强加规则来在接口中放置一个操作.理想情况下,可以读取,写入,删除的接口可以完成(但不具有相关操作的笨重)接口.这里,接口中的操作应该相互关联,而不是相互依赖.

所以,传统上,你可以有一个像这样的界面

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);}

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

此外,如果您的业务/应用程序层类仅依赖于此接口,而不是实际的实现类,就像这样

class EmployeeService{ readonly IRepository<Employee> _employeeRepo; Employee GetEmployeeById(int id) { return _employeeRepo.Read(id); } //other CRUD operation on employee}

然后,您的业务/应用程序类将完全独立于数据存储基础结构.您可以灵活地选择自己喜欢的数据存储,只需将其插入代码库并实现此接口即可.

您可以使用OracleRepository:IRepository和/或MongoRepository:IRepository,并在需要时通过IoC注入正确的文件.