C#中的重用抽象原则

Reused abstraction principle in C#

在我们的 C# MVC 应用程序中,我们有很多接口与实现它们的对象一对一映射。即:基本上,对于创建的每个对象,都执行了一个 "extract interface" 操作。

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

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

public interface IEmployee: IAdd<Employee>, IUpdate<Employee>, IFinder<Employee>

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

至少它会保持方法的签名一致。但这还能给我带来什么其他好处呢? (抛开里氏替换原则)

现在,方法的名称和 return 类型到处都是。

我读了 Mark Seemann 关于重用抽象原则的博客,但坦率地说,我不明白。也许我只是愚蠢 :) 我还阅读了 Fowler 对 Header Interfaces 的定义。

所有这些都可以使用 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; }
} 

编辑

No concrete objects in our system implement multiple interfaces.

Can anyone tell me if this is going to cause problems down the road? And if so, what would they be?

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

您最终会得到一堆接口,这些接口对您的解决方案没有任何帮助,而且会耗费您大量的时间来维护和创建它们。随着您的代码库规模的增加,您会发现并不是所有的东西都像您曾经想象的那样内聚

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

你总结了这个,

This isn't about the repository pattern - this is about interfaces in any layer that look like they share semantically identical behaviours. Is it worth deriving common interfaces for these operations and making "sub-interfaces" inherit from them?

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

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

Repository pattern 的概念很明显,因为它定义了一组行为,这些行为由许多不同的 类 实现,每个都针对特定实体量身定制。

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

现在我们可以将我们从存储库模式中学到的东西应用到我们应用程序的其他部分,由此我们定义了一组核心行为,这些行为由新界面中的许多对象共享,然后从该接口派生。

public interface IVehicleOperator<TVehicle> where TVehicle : IVehicle
{
    void Accelerate();
    void Brake();
}

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

虽然我们正在讨论这个主题,但 decorator pattern 也可能值得回顾一下。

鉴于此:

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

我可能会像这样重新设计它:

interface IRepository<T>
{
    void Add(T toAdd);
    void Update(T toUpdate);
    T GetById(int id);
}

但是,这很可能仍然违反了 Interface Segregation Principle, not to mention that since it also violates the Command-Query Responsibility Segregation 模式(不是体系结构),它也不能既不协变也不协变。

因此,我的下一步可能是将它们拆分为 Role Interfaces:

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

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

interface IReader<T>
{
    T GetById(int id);
}

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

interface ICommand<T>
{
    void Execute(T item);
}

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

interface IQuery<T>
{
    T GetById(int id);
}

本质上,你可以将一切缩减为这两个接口,但对于某些人来说,这可能过于抽象,并且携带的语义信息太少。

但是,我认为不可能提供更好的答案,因为前提是有缺陷的。最初的问题是界面应该如何设计,但是客户端却遥遥无期。作为 APPP 通道。 11 告诉我们,"clients [...] own the abstract interfaces" - 换句话说,客户端根据需要定义接口。不应从具体 类.

中提取接口

有关此主题的进一步学习材料: