【问题标题】:Dependencies injection without constructor: really a bad practice?没有构造函数的依赖注入:真的是个坏习惯吗?
【发布时间】:2012-08-02 12:13:21
【问题描述】:

我正在使用 C#、MVC4、StructureMap 等开发 Web 解决方案。

在解决方案中,我为控制器提供服务。举例:

public class ServiceA{
    private readonly IRepository _repository1;
    private readonly IRepository _repository2;

    public ServiceA(IRepository1 repository1, IRepository2 repository2){
        _repository1=repository1;
        _repository2=repository2;
    }

    public void DoSomethingA(){
        _repository1.DoSomething();
    }

    public void DoSomethingB(){
        _repository2.DoSomething();
    }
}

public class ServiceB{
    private readonly IRepository _repository3;
    private readonly IRepository _repository4;

    public ServiceB(IRepository3 repository3, IRepository4 repository4){
        _repository3=repository3;
        _repository4=repository4;
    }

    public void DoSomethingA(){
        _repository3.DoSomething();
    }

    public void DoSomethingB(){
        _repository4.DoSomething();
    }
}

这样做是个好习惯吗? :

public abstract class ServiceBase(){
    public IRepository1 Repository1 { get { return instanceOf<IRepository1>(); }}
    public IRepository2 Repository2 { get { return instanceOf<IRepository2>(); }}
    public IRepository3 Repository3 { get { return instanceOf<IRepository3>(); }}
    public IRepository4 Repository4 { get { return instanceOf<IRepository4>(); }}

    private T instanceOf<T>()
    {
        return ServiceLocator.Current.GetInstance<T>();
    }
}

然后以这种方式创建服务?

public class ServiceA : ServiceBase
{
    public void DoSomethingA(){
        Repository1.DoSomething();
    }

    public void DoSomethingB(){
        Repository2.DoSomething();
    }
}


public class ServiceB : ServiceBase
{
    public void DoSomethingA(){
        Repository3.DoSomething();
    }
    public void DoSomethingB(){
        Repository4.DoSomething();
    }
}

使用第二个选项,我看到了某些优势:

  • 不必为每个存储库都有一个私有变量。
  • 我不需要服务的构造函数,使它们更小更易于阅读。
  • 所有存储库都可以在任何服务中使用。
  • 服务没有获得不必要的实例。例如,在ServiceA 方法DoSomethingA 中调用ServiceLocator 只得到Repository1 实例。 (使用第一种方法会收到两个实例:Repository1Repository2

在这两种情况下,我都可以进行适当的测试:

  • 在第一种情况下,通过构造函数发送模拟对象。
  • 在第二种情况下,将 StructureMap 配置为在必要时使用模拟对象。

你觉得呢?我违背了一些原则? (对不起我的英语)

【问题讨论】:

    标签: c# asp.net-mvc dependency-injection structuremap recommendation-engine


    【解决方案1】:

    我看到这种设计的问题是您将 ServiceLocator 紧密耦合到您的服务,因此不提倡松散耦合。如果您想使用模拟/测试存储库对服务进行单元测试,会发生什么?甚至换掉你的 DI 实现。实际上,这可能不是什么大问题,因为您可以通过服务定位器配置您的测试服务,但对我来说它只是感觉代码有异味。

    【讨论】:

    • 谢谢林哥!你的答案和雷莫的一样,只是有点短,所以我把它拨成正确的,但两者都是正确的。问候!
    【解决方案2】:

    让我们先看看优势论点:

    不必为每个存储库都有一个私有变量。

    没错。虽然实际上这 4 个字节作为参考通常并不重要。

    我不需要服务的构造函数,使它们更小并且 更容易阅读。

    我的看法正好相反。拥有一个构造函数会立即告诉您该类具有哪些依赖项。对于基类,您必须查看整个类以获取该信息。此外,如果您的设计具有低耦合、高内聚和无缠结,则无法使用工具进行分析。而那些使用这个类的人根本不知道这个类有什么依赖关系,除非他们阅读了实现。

    所有存储库都可以在任何服务中使用。

    应避免在一项服务中使用多个 repos,因为这会增加耦合。为所有服务提供所有 repos 是鼓励高耦合不良设计的最佳方式。所以我认为这是一个缺点。

    服务不会获得不必要的实例。例如,调用 ServiceA 方法 DoSomethingA ServiceLocator 只获取 Repository1 实例。 (使用第一种方法会收到两个 实例:对于 Repository1 和 Repository2 )

    使用完全不同依赖项不同方法的服务是一个巨大的迹象,表明它不遵循Single Responsibility Principle。在这种情况下,它很可能会被拆分为两个服务。

    关于可测试性:在第二种情况下,通过使用单例 (ServiceLocator),您的测试不再是孤立的。所以他们可以互相影响。尤其是并行运行时。

    在我看来,你走错了路。使用Service Locator anti-pattern,您将依赖项隐藏到使用您的类的人身上,使阅读实现的人更难看到该类具有哪些依赖项,并且您的测试不再被隔离。

    【讨论】:

    • 谢谢雷莫!你的几行 cmets 为我节省了很多时间和头痛!你的意见和朋友给我的一模一样,只是想在这里问一下,因为我不太了解其中的缺点。现在我看得更清楚了。非常感谢您的宝贵时间。问候。
    • @remo-gloor +1 以获得详细答案,但是当您需要 Transient 依赖项时,这是一个性能问题,例如,如果服务需要像 AWS S3 或类似的远程存储。这将不必要地为所有控制器创建/处置实例,即使它们可能不会在单个请求范围内被调用。使用Get&lt;T&gt;() 并使用具有此类所需类型的属性装饰类怎么样,Get&lt;T&gt;() 只会返回装饰类型的实例。或者公共属性注入,当然可以利用getter的优势来使用延迟加载。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-02
    • 2020-10-24
    • 2018-06-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多