9

我开始了学习依赖注入的旅程,我看到为什么使用 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();
}

?

请记住,我对这整个概念很陌生,所以要友善:)

4

3 回答 3

6

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

请注意,“接口”一词背后隐藏着两个概念,这使得在 C# 中推理变得更加困难:

  • “接口”和抽象概念:定义明确的一组与对象交互的属性/方法;与对象一起工作的合同。
  • “接口”作为特定语言(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...
于 2012-07-19T21:26:34.350 回答
5

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

本质上,您可以提供一个实现多个接口的类,然后可以将其注入到多个类中,但您将只有一个抽象基类(至少在 C# 中)。

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

这个 SO 将涵盖更多关于接口与基类的方面

于 2012-07-19T21:31:27.623 回答
1

在 .NET 中,您只有单一继承。在这种情况下的语言/框架中,选择使用抽象类作为抽象可能会“烧毁基类”

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

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

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

更糟糕的是,如果您无权访问 modify Base,那么您可能不得不求助于一些非常丑陋的解决方法(即反射)。

也就是说,使用接口,您可以让实现者选择从哪里继承,而不是限制他们的选择。

您将看到的典型模式是您始终为您的合约提供一个接口,并针对该接口对您的消费者进行编码。您提供了一个抽象基类,该基类提供人们为了方便而可以派生的功能,但没有义务从.

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

于 2012-07-19T21:48:06.290 回答