C# 接口隔离和单一责任原则的困境

C# 接口隔离和单一责任原则的困境,c#,interface,solid-principles,single-responsibility-principle,interface-segregation-principle,C#,Interface,Solid Principles,Single Responsibility Principle,Interface Segregation Principle,我试图遵循接口隔离和单一责任原则,但我对如何将其结合起来感到困惑 这里我举了几个接口的例子,我将它们分为更小、更直接的接口: public interface IDataRead { TModel Get<TModel>(int id); } public interface IDataWrite { void Save<TModel>(TModel model); } public interface IDataDelete {

我试图遵循接口隔离单一责任原则,但我对如何将其结合起来感到困惑

这里我举了几个接口的例子,我将它们分为更小、更直接的接口:

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);
}
/// <summary>
/// Inform an underlying data store to return a set of read-only entity instances.
/// </summary>
/// <typeparam name="TEntity">The entity type to return read-only entity instances of.</typeparam>
public interface IEntityReader<out TEntity> where TEntity : Entity
{
    /// <summary>
    /// Inform an underlying data store to return a set of read-only entity instances.
    /// </summary>
    /// <returns>IQueryable for set of read-only TEntity instances from an underlying data store.</returns>
    IQueryable<TEntity> Query();
}

/// <summary>
/// Informs an underlying  data store to accept sets of writeable entity instances.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IEntityWriter<in TEntity> where TEntity : Entity
{
    /// <summary>
    /// Inform an underlying data store to return a single writable entity instance.
    /// </summary>
    /// <param name="primaryKey">Primary key value of the entity instance that the underlying data store should return.</param>
    /// <returns>A single writable entity instance whose primary key matches the argument value(, if one exists in the underlying data store. Otherwise, null.</returns>
    TEntity Get(object primaryKey);

    /// <summary>
    /// Inform the underlying  data store that a new entity instance should be added to a set of entity instances.
    /// </summary>
    /// <param name="entity">Entity instance that should be added to the TEntity set by the underlying data store.</param>
    void Create(TEntity entity);

    /// <summary>
    /// Inform the underlying data store that an existing entity instance should be permanently removed from its set of entity instances.
    /// </summary>
    /// <param name="entity">Entity instance that should be permanently removed from the TEntity set by the underlying data store.</param>
    void Delete(TEntity entity);

    /// <summary>
    /// Inform the underlying data store that an existing entity instance's data state may have changed.
    /// </summary>
    /// <param name="entity">Entity instance whose data state may be different from that of the underlying data store.</param>
    void Update(TEntity entity);
}

/// <summary>
/// Synchronizes data state changes with an underlying data store.
/// </summary>
public interface IUnitOfWork
{
    /// <summary>
    /// Saves changes tot the underlying data store
    /// </summary>
    void SaveChanges();
}
internal sealed class EntityFrameworkRepository<TEntity> : 
    IEntityReader<TEntity>, 
    IEntityWriter<TEntity>, 
    IUnitOfWork where TEntity : Entity
{
    private readonly Func<DbContext> _contextProvider;

    public EntityFrameworkRepository(Func<DbContext> contextProvider)
    {
        _contextProvider = contextProvider;
    }

    public void Create(TEntity entity)
    {
        var context = _contextProvider();
        if (context.Entry(entity).State == EntityState.Detached)
        {
            context.Set<TEntity>().Add(entity);
        }
    }

    public void Delete(TEntity entity)
    {
        var context = _contextProvider();
        if (context.Entry(entity).State != EntityState.Deleted)
        {
            context.Set<TEntity>().Remove(entity);
        }  
    }

    public void Update(TEntity entity)
    {
        var entry = _contextProvider().Entry(entity);
        entry.State = EntityState.Modified;
    }

    public IQueryable<TEntity> Query()
    {
        return _contextProvider().Set<TEntity>().AsNoTracking();
    }

    public TEntity Get(object primaryKey)
    {
        return _contextProvider().Set<TEntity>().Find(primaryKey);
    }

    public void SaveChanges()
    {
        _contextProvider().SaveChanges();
    }
}
现在,我遇到了一个问题,我确信总体思路是创建一个
数据库
类,它使用接口,而不是类(真正的实现)。所以,我想出了这样的办法:

public class Database
{
    IDataDelete _Deleter;
    ...

    //Injecting the interfaces to make use of Dependency Injection.
    public Database(IDataRead reader, IDataWrite writer, IDataDelete deleter) { ... }
}
public IDataDelete Deleter { get; private set; }
这里的问题是如何向客户端公开
IDataRead
IDataWrite
IDataDelete
接口?我应该重写方法以重定向到接口吗?像这样:

//This feels like I'm just repeating a load of work.
public void Delete<TModel>(TModel model)
{
    _Deleter.Delete<TModel>(model);
}
这感觉稍微好一点,但是,不应该期望客户机必须经历决定他们需要使用哪个接口的麻烦


我完全没有抓住要点吗?救命啊

在本例中,如果您想基于接口的组合定义对象的功能,则分解每种类型的操作的能力将是

所以你可以有一些只能获取的东西,还有一些可以获取、保存和删除的东西,还有一些只能保存的东西。然后,您可以将它们传递到对象中,这些对象的方法或构造函数只调用isave,或者其他什么。这样一来,他们就不用担心知道某个东西是如何保存的,只需要知道它是如何保存的,这是通过接口公开的Save()方法调用的

或者,您可以有这样一种场景:数据库实现所有接口,但随后它被传递给只关心触发写入、读取或更新的对象,等等——因此,当它被传递到该对象时,它被作为适当的接口类型传递,并且执行其他操作的能力不会向使用者公开

考虑到这一点,您的应用程序很可能不需要这种类型的功能。您可能不是在使用来自不同来源的数据,而是需要抽象一种跨来源调用CRUD操作的通用方法(第一种方法将解决此问题),或者需要将数据库作为数据源的概念与支持CRUD ops的对象分离(第二种方法将解决此问题)。因此,请务必确保这是为了满足需要而采用的,而不是为了遵循最佳实践——因为这只是采用某种实践的一种方式,但它是否“最佳”只能在解决问题的背景下确定

我完全没有抓住要点吗?救命啊

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


如果,作为您功能的消费者,我必须为插入、删除等实例化不同的类,我会来找您

这不是一个真正的答案,但我想在这里说的比评论所允许的更多。感觉就像是在使用存储库模式,所以可以用IRepository来总结

interface IRepository
{
    T Get<TModel>(int id);
    T Save<TModel>(TModel model);
    void Delete<TModel>(TModel model);
    void Delete<TModel>(int id);
}
接口假定
{
T Get(int-id);
T保存(TModel模型);
作废删除(TModel模型);
无效删除(int-id);
}
现在,您可以像上面一样拥有一个具体的数据库:

class Database : IRepository
{
    private readonly IDataReader _reader;
    private readonly IDataWriter _writer;
    private readonly IDataDeleter _deleter;

    public Database(IDataReader reader, IDataWriter writer, IDataDeleter deleter)
    {
        _reader = reader;
        _writer = writer;
        _deleter = deleter;
    }

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

    public T Save<TModel>(TModel model) { _writer.Save<TModel>(model); }

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

    public void Delete<TModel>(int id) { _deleter.Delete<TModel>(id); }
}
类数据库:IRepository
{
专用只读IDataReader\u阅读器;
私有只读IDataWriteru writer;
专用只读IDataDeleter\u deleter;
公共数据库(IDataReader、IDataWriter、IDataDeleter)
{
_读取器=读取器;
_作家=作家;
_deleter=deleter;
}
公共T Get(int id){u reader.Get(id);}
公共T保存(TModel模型){u writer.Save(model);}
公共void Delete(TModel模型){u deleter.Delete(model);}
public void Delete(int-id){{u deleter.Delete(id);}
}
是的,从表面上看,这似乎是一种不必要的抽象,但有很多好处。正如@moarboilerplate所说,这是他的答案,不要让“最佳”实践妨碍产品的交付。您的产品规定了您需要遵循的原则以及产品所需的抽象级别

以下是采用上述方法的一个快速好处:

class CompositeWriter : IDataWriter
{
    public List<IDataWriter> Writers { get; set; }

    public void Save<TModel>(model)
    {
        this.Writers.ForEach(writer =>
        {
            writer.Save<TModel>(model);
        });
    }
}

class Database : IRepository
{
    private readonly IDataReader _reader;
    private readonly IDataWriter _writer;
    private readonly IDataDeleter _deleter;
    private readonly ILogger _logger;

    public Database(IDataReader reader, IDataWriter writer, IDataDeleter deleter, ILogger _logger)
    {
        _reader = reader;
        _writer = writer;
        _deleter = deleter;
        _logger = logger;
    }

    public T Get<TModel>(int id)
    {
        var sw = Stopwatch.StartNew();

        _writer.Get<TModel>(id);

        sw.Stop();

        _logger.Info("Get Time: " + sw. ElapsedMilliseconds);
    }

    public T Save<TModel>(TModel model)
    {
         //this will execute the Save method for every writer in the CompositeWriter
         _writer.Save<TModel>(model);
    }

    ... other methods omitted
}
类复合编写器:IDataWriter
{
公共列表写入程序{get;set;}
公共作废保存(模型)
{
this.Writers.ForEach(writer=>
{
writer.Save(模型);
});
}
}
类数据库:IRepository
{
专用只读IDataReader\u阅读器;
私有只读IDataWriteru writer;
专用只读IDataDeleter\u deleter;
专用只读ILogger\u记录器;
公共数据库(IDataReader、IDataWriter-writer、IDataDeleter、ILogger\u记录器)
{
_读取器=读取器;
_作家=作家;
_deleter=deleter;
_记录器=记录器;
}
公共T获取(int id)
{
var sw=Stopwatch.StartNew();
_writer.Get(id);
sw.Stop();
_logger.Info(“获取时间:+sw.elapsedmillyses”);
}
公共T保存(TModel模型)
{
//这将对CompositeWriter中的每个writer执行Save方法
_writer.Save(模型);
}
…省略了其他方法
}
现在,您可以有地方扩展功能。上面的示例演示了如何使用不同的IDataReader并对其计时,而不必在每个IDataReader中添加日志记录和计时。这还展示了如何使用复合IDataWriter将数据实际存储到多个stor中
class EmployeeService
{
    readonly IRepository<Employee> _employeeRepo;

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

    //other CRUD operation on employee
}
public class Database : IDataRead, IDataWrite, IDataDelete
{
}
public interface IRepository : IDataRead, IDataWrite, IDataDelete
{
}
/// <summary>
/// Inform an underlying data store to return a set of read-only entity instances.
/// </summary>
/// <typeparam name="TEntity">The entity type to return read-only entity instances of.</typeparam>
public interface IEntityReader<out TEntity> where TEntity : Entity
{
    /// <summary>
    /// Inform an underlying data store to return a set of read-only entity instances.
    /// </summary>
    /// <returns>IQueryable for set of read-only TEntity instances from an underlying data store.</returns>
    IQueryable<TEntity> Query();
}

/// <summary>
/// Informs an underlying  data store to accept sets of writeable entity instances.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IEntityWriter<in TEntity> where TEntity : Entity
{
    /// <summary>
    /// Inform an underlying data store to return a single writable entity instance.
    /// </summary>
    /// <param name="primaryKey">Primary key value of the entity instance that the underlying data store should return.</param>
    /// <returns>A single writable entity instance whose primary key matches the argument value(, if one exists in the underlying data store. Otherwise, null.</returns>
    TEntity Get(object primaryKey);

    /// <summary>
    /// Inform the underlying  data store that a new entity instance should be added to a set of entity instances.
    /// </summary>
    /// <param name="entity">Entity instance that should be added to the TEntity set by the underlying data store.</param>
    void Create(TEntity entity);

    /// <summary>
    /// Inform the underlying data store that an existing entity instance should be permanently removed from its set of entity instances.
    /// </summary>
    /// <param name="entity">Entity instance that should be permanently removed from the TEntity set by the underlying data store.</param>
    void Delete(TEntity entity);

    /// <summary>
    /// Inform the underlying data store that an existing entity instance's data state may have changed.
    /// </summary>
    /// <param name="entity">Entity instance whose data state may be different from that of the underlying data store.</param>
    void Update(TEntity entity);
}

/// <summary>
/// Synchronizes data state changes with an underlying data store.
/// </summary>
public interface IUnitOfWork
{
    /// <summary>
    /// Saves changes tot the underlying data store
    /// </summary>
    void SaveChanges();
}
internal sealed class EntityFrameworkRepository<TEntity> : 
    IEntityReader<TEntity>, 
    IEntityWriter<TEntity>, 
    IUnitOfWork where TEntity : Entity
{
    private readonly Func<DbContext> _contextProvider;

    public EntityFrameworkRepository(Func<DbContext> contextProvider)
    {
        _contextProvider = contextProvider;
    }

    public void Create(TEntity entity)
    {
        var context = _contextProvider();
        if (context.Entry(entity).State == EntityState.Detached)
        {
            context.Set<TEntity>().Add(entity);
        }
    }

    public void Delete(TEntity entity)
    {
        var context = _contextProvider();
        if (context.Entry(entity).State != EntityState.Deleted)
        {
            context.Set<TEntity>().Remove(entity);
        }  
    }

    public void Update(TEntity entity)
    {
        var entry = _contextProvider().Entry(entity);
        entry.State = EntityState.Modified;
    }

    public IQueryable<TEntity> Query()
    {
        return _contextProvider().Set<TEntity>().AsNoTracking();
    }

    public TEntity Get(object primaryKey)
    {
        return _contextProvider().Set<TEntity>().Find(primaryKey);
    }

    public void SaveChanges()
    {
        _contextProvider().SaveChanges();
    }
}