C#中重用的抽象原则

Sco*_*ott 12 c# oop abstraction solid-principles

在我们的C#MVC应用程序中,我们有很多接口,它们将实现它们的对象映射为1到1.即:基本上,对于创建的每个对象,已经执行了"提取界面"操作.

Moq使用这些接口为我们的单元测试生成模拟对象.但这是接口重复使用的唯一时间.

我们系统中没有具体对象实现多个接口.

谁能告诉我这是否会导致问题在路上?如果是这样,他们会是什么?

我在想,我们的应用程序有很多重复,例如在这两个接口(编辑:在我们的服务层)中,唯一不同的是方法名称和它们所采用的参数类型,但在语义上他们做与他们发送消息的存储库相同:

interface ICustomer
{
    void AddCustomer(Customer toAdd);
    void UpdateCustomer(Customer toUpdate);
    Customer GetById(int customerId);
}

interface IEmployee
{
    void AddEmployee(Employee toBeAdded);
    void UpdateEmployee(Employee toUpdate);
    Employee GetById(int employeeId);       
}
Run Code Online (Sandbox Code Playgroud)

这就是我认为重用的抽象原则会出现的地方,即将代码转换为类似的东西:

public interface IEmployee: IAdd<Employee>, IUpdate<Employee>, IFinder<Employee>
Run Code Online (Sandbox Code Playgroud)

这与存储库模式无关 - 这是关于任何层中的接口,看起来它们共享语义相同的行为.是否值得为这些操作派生通用接口并使"子接口"继承它们?

至少它会保持方法的签名一致.但这会给我带来什么其他好处呢?(除了利斯科夫替代原则)

现在,方法的名称和返回类型都到处都是.

我读过Mark Seemann关于Reused抽象原理的博客,但我不明白,坦白说.也许我只是愚蠢:)我还阅读了Fowler对Header Interfaces的定义.

Ayd*_*din 10

所有这一切都可以使用Repository pattern......

public interface IRepository<TEntity> where TEntity : IEntity
{
    T FindById(string Id);
    T Create(T t);
    bool Update(T t);
    bool Delete(T t);
}

public interface IEntity
{
    string Id { get; set; }
} 
Run Code Online (Sandbox Code Playgroud)

编辑

我们系统中没有具体对象实现多个接口.

谁能告诉我这是否会导致问题在路上?如果是这样,他们会是什么?

是的,如果还没有开始这样做会导致问题.

您最终会得到一堆接口,这些接口不会为您的解决方案增加任何内容,从而耗费大量时间来维护和创建它们.随着代码库的大小增加,您会发现并非所有内容都像您曾经想象的那样具有凝聚力

请记住,接口只是一种工具,是实现抽象级别的工具.而抽象是一个概念,一个模式,一个由许多独立实体共享的原型.

你总结了这个,

这与存储库模式无关 - 这是关于任何层中的接口,看起来它们共享语义相同的行为.是否值得为这些操作派生通用接口并使"子接口"继承它们?

这不是关于interfaces,这是关于abstractions,Repository pattern演示了如何抽象出针对特定对象定制的行为.

我上面给出的示例没有任何命名的方法AddEmployeeUpdateEmployee...这样的方法只是浅层接口,而不是抽象.

的概念Repository pattern是,它定义了一组由多个不同类的,每一个用于一个特定实体定制实施行为的明显.

考虑到为每个实体(UserRepository,BlogRepository等)实现了存储库,并且考虑到每个存储库必须支持一组核心功能(基本CRUD操作),我们可以采用该核心功能集并在接口中定义它,然后在每个存储库中实现该接口.

现在我们可以从Repository模式中学到一些东西,并将它应用到我们应用程序的其他部分,从而定义一个由新接口中的多个对象共享的核心行为集,然后从中派生出来.接口.

public interface IVehicleOperator<TVehicle> where TVehicle : IVehicle
{
    void Accelerate();
    void Brake();
}
Run Code Online (Sandbox Code Playgroud)

在这样做时,我们不再有1:1的映射,而是实际的抽象.

虽然我们讨论这个话题,但也值得一看decorator pattern.


Mar*_*ann 10

鉴于这种:

interface ICustomer{
    void AddCustomer(Customer toAdd);
    void UpdateCustomer(Customer toUpdate);
    Customer GetById(int customerId);
}

interface IEmployee
{
    void AddEmployee(Employee toBeAdded);
    void UpdateEmployee(Employee toUpdate);
    Employee GetById(int employeeId);       
}
Run Code Online (Sandbox Code Playgroud)

我可能会首先重新设计它:

interface IRepository<T>
{
    void Add(T toAdd);
    void Update(T toUpdate);
    T GetById(int id);
}
Run Code Online (Sandbox Code Playgroud)

但是,这可能仍然违反了接口隔离原则,更不用说因为它也违反了命令查询责任隔离 模式(不是架构),所以它也不能同时也不能成为逆变量.

因此,我的下一步可能是将它们拆分为角色接口:

interface IAdder<T>
{
    void Add(T toAdd);
}

interface IUpdater<T>
{
    void Update(T toAdd);
}

interface IReader<T>
{
    T GetById(int id);
}
Run Code Online (Sandbox Code Playgroud)

此外,您可能会注意到IAdder<T>并且IUpdater<T>在结构上是相同的(它们仅在语义上不同),所以为什么不将它们设为一个:

interface ICommand<T>
{
    void Execute(T item);
}
Run Code Online (Sandbox Code Playgroud)

为了保持一致,你也可以重命名IReader<T>:

interface IQuery<T>
{
    T GetById(int id);
}
Run Code Online (Sandbox Code Playgroud)

从本质上讲,您可以减少这两个接口的所有内容,但对于某些人来说,这可能过于抽象,并且携带的语义信息太少.

但是,我认为不可能提供更好的答案,因为前提是有缺陷的.最初的问题是如何设计界面,但客户端无处可见.作为APPP ch.11告诉我们,"客户端[...]拥有抽象接口" - 换句话说,客户端根据需要定义接口.不应从具体类中提取接口.

关于这个主题的进一步研究材料

  • RAP主要描述了代码库的理想质量.缺乏重用的抽象通常是*代码库违反[LSP](http://en.wikipedia.org/wiki/Liskov_substitution_principle)的*症状.那不是*必然*坏.SOLID原则的存在是为了解决代码的某些问题(主要是可维护性).如果您没有这些问题,则没有理由尝试应用"解决方案". (3认同)