C#中重用的抽象原则

时间:2015-05-06 13:33:07

标签: 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);       
}

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

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

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

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

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

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

2 个答案:

答案 0 :(得分: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; }
} 

修改

  

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

     

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

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

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

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

你总结了这个,

  

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

这与interfaces无关,约为abstractionsRepository pattern演示了如何抽象出针对特定对象的行为。

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

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

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

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

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

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

虽然我们正在谈论这个话题,但也值得回顾decorator pattern

答案 1 :(得分:9)

鉴于此:

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,更不用说因为它也违反了Command-Query Responsibility Segregation 模式(不是架构),它也可以&# 39;既不是合作也不是逆变。

因此,我的下一步可能是将它们分成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 ch。 11教我们,&#34;客户端[...]拥有抽象接口&#34; - 换句话说,客户端根据需要定义接口。接口不应该从具体类中提取出来。

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