在C#

时间:2016-10-27 14:47:33

标签: c# oop dependency-injection

我知道你应该依赖于抽象而不是具体的实现,但我也知道YAGNI原则。我有时会发现自己正在努力调和这两者。

考虑以下类别;

public class Foo
{
    public void DoFoo()
    {
    }
    //private foo stuff
}

public class Bar
{
    private readonly Foo _foo;

    public Bar()
    {
        _foo = new Foo();
    }
}

"酒吧"是我感兴趣的班级;显然有一个问题,Bar正在实例化一个Foo的实例,所以让我重构一下;

public class Bar
{
    private readonly Foo _foo;

    public Bar(Foo foo)
    {
        _foo = foo;
    }
}

很好,但是Bar的构造函数仍然依赖于Foo,一个具体的实现。我没有获得任何东西(有吗?)。为了解决这个问题,我需要让foo成为一个抽象,这就是我的问题开始的地方。

我发现的每个例子(可以理解)都使用抽象来演示构造函数注入。我只是为了防御性编程,但我认为除了Foo之外我不需要任何其他的实现(测试双打不算数)。创建一个" IFoo"界面或" FooBase"抽象类肯定违反了YAGNI原则?我会为可能的未来情况做点什么,我可以随时做到这一点,例如。

public abstract class Foo
{
    public abstract void DoFoo();

    //private foo stuff
}

public class Foo1:Foo
{
    public override void DoFoo()
    {
    }
}

这并没有打破Bar,我甚至可以为界面做这个,只要我放弃了"我"惯例(我越来越怀疑)例如。

public interface Foo
{
    void DoFoo();
}

public abstract class FooBase:Foo
{
    public abstract void DoFoo();

    //private foo stuff
}

public class Foo1:FooBase
{
    public override void DoFoo()
    {
    }
}

注入具体实现有什么问题,因为我可以在稍后阶段将它重构为抽象(假设我给抽象提供了与具体实现相同的名称)?

注意:我知道" I"接口命名约定,这不是我的问题。我也知道将Foo作为一个抽象类会破坏代码,无论我以前在哪里实例化它,但假设我广泛使用DI,所以我只需要更改DI容器注册,无论如何我都可能要做的事情。我将介绍一个新的Foo实现。

3 个答案:

答案 0 :(得分:4)

  

但是Bar的构造函数仍然依赖于Foo,一个具体的实现。我没有获得任何东西(有吗?)。

您在此获得的是,当依赖关系Foo本身获得其自身的依赖关系或需要不同的生活方式时,您可以进行此更改,而无需在{{1}的所有消费者中进行彻底的更改}。

  

我不需要除Foo之外的任何其他实现(测试双打不算数)

你不能忽略单元测试。在很久以前的Roy Osherove explained,您的测试套件是您的应用程序的另一个(同样重要的)消费者,具有自己的要求。如果添加抽象简化了测试,那么您就不需要其他原因来创建它。

  

创建一个" IFoo"界面或" FooBase"抽象课肯定违反了YAGNI原则?

如果您为测试创建此抽象,则不会违反YAGNI。在那种情况下YNI(你需要它)。通过不创建抽象,您将在生产代码中进行本地优化。这是局部最优而不是全局最优,因为这种优化不会考虑所有其他(同样重要的)需要维护的代码(即您的测试代码)。

  

注入具体实现有什么问题,因为我可以将它重构为抽象

每次注入注入具体实例都没有错,尽管如此 - 创建一个抽象可以简化测试。如果它没有简化测试并且让消费者对实现有严格的依赖可能没问题。但要注意,根据具体类型可能会有其缺点。例如,用不同的实例(例如拦截器或装饰器)替换它变得更加困难,而不必对消费者进行更改。如果这不是问题,您也可以使用具体类型。

答案 1 :(得分:2)

正如adaam所说,做你想做的事并没有错。

您使用的DI容器是什么?如果您使用Unity,PRISM有一个很好的例子,说明如何将ViewModel注册为BindableBase(PRISM提供的基类),除非您有一个实现其他接口的基类。

通常我有一个BaseViewModel,它扩展了BindableBase并实现了INotifyDataErrorInfo和其他一些接口。然后,当发现模块时,它们将ViewModel注册为BaseViewModel的类型。

答案 2 :(得分:1)

Foo的构造函数中提供Bar仍然可以提供与在Bar中实例化相比的内容,即使只是具体的实现。假设您要测试Bar,并假设您设计Foo的方式是可能导致单元测试问题的所有功能都放在虚拟方法中。然后在你的单元测试中继承Foo,覆盖必要的成员,然后将你继承的类的实例传递给Bar构造函数,如果你在Foo内实例化Bar,这显然是不可能的。 {1}}本身。与DI相同的故事 - 您可以在DI容器中将从Foo继承的类注册为Foo