为什么使用接口而不是DI的抽象类?

时间:2012-07-19 21:22:00

标签: c# dependency-injection

我开始学习依赖注入的过程,并且我看到为什么使用DI是个好主意的原因之一是它明确指定了你的依赖关系,这也使你的代码更加清晰。

我也注意到接口被大量使用,但我想知道为什么我们不会仅仅为了指定默认构造函数而使用抽象类?

当然,抽象类中不能包含任何实现。

不会这样:

abstract class FooBase
{
    protected IBar _bar;

    FooBase(IBar bar)
    {
        _bar = bar;
    }

    abstract void DoSomething();
    abstract void DoSomethingElse();
}

更清楚地证明FooBase对象的依赖性超过:

interface IFoo
{
    IBar Bar { get; }

    void DoSomething();
    void DoSomethingElse();
}

请记住,我是这个概念的新手所以要好一点:)

3 个答案:

答案 0 :(得分:5)

一个技术原因 - 在没有多重继承的语言中强制使用特定父类(Java / C#)将严重限制“接口”的实现自由。

请注意,“接口”字后面隐藏着2个概念,这使得在C#中更难理解:

  • “interface”和抽象概念:明确定义的一组与对象交互的属性/方法;与对象合作的合同。
  • “interface”作为特定语言的类型(C#/ Java) - 一种可能的表示形式 用给定语言签订合同。

抽象/具体类可用于表示契约,但强制限制C#中的契约实施者。

其他一些语言(如C ++)没有这样的限制,抽象类是很好的选择。其他语言(即“duck-types”JavaScript)没有这样的类/接口区别,但你仍然可以与对象“契约”。

样品:

为了提供更多你应该在C#中实现此限制的上下文:DI通常与某种形式的TDD一起使用,或者至少与基本单元测试一起使用。因此,您尝试编写一些使用DI的抽象基类的代码和测试。

abstract class Duck { 
  abstract void Quack();
}
class ConcreteDuck : Duck {...}

现在,如果您决定编写测试,您可能已经拥有可以帮助您模拟对象的测试类(如果您没有使用现有的一次)

class MockBase {...}

class MockDuck : MockBase,?????? // Can't use Duck and MockBase together...

答案 1 :(得分:5)

接口定义合同。 Abstract基类定义了一种行为。

基本上,您可以提供一个实现多个接口的类, 然后反过来可以注入多个类,但你只会注入 单个抽象基类(至少在C#中)。

考虑在容器上注册类型的点(最好是组合根) 并考虑解决依赖关系的点(构造函数或属性)。

此SO将涵盖更多方面SO on interface vs base class

答案 2 :(得分:1)

在.NET中,您只有单继承。在这种情况下的语言/框架中,选择使用抽象类作为抽象提供了"burn the base class"的潜力。

这意味着您强制抽象的实现者从单个类继承,强制它们这样做可能会导致严重不便,具体取决于实现的内容。

假设您有抽象类Contract。如果某人有自己想要使用的Base类,那么只公开protected方法(对于继承者)。

因为方法是protected,所以不能使用封装(存储在字段中的Base的实例)来访问Base中用于抽象实现的方法。

更糟糕的是,如果您无权修改Base,那么您可能不得不求助于一些非常难看的变通方法(反思,即)。

也就是说,通过接口,您可以为实现者选择继承的位置,而不是限制他们的选择。

您将看到的典型模式是,您始终为合同提供界面,并根据界面对您的消费者进行编码。您提供了一个抽象基类,它提供了人们可能从中派生的功能,但不是有义务从派生的。

另外,如果你可以以容易封装的形式提供这个功能(对于我上面描述的条件),它会更加优化(你有一个抽象类只调用暴露方法的实例。)