我得到依赖注入,但是有人可以帮助我了解对IoC容器的需求吗?


15

如果这似乎又是一个重复的问题,我深表歉意,但是每次我找到有关该主题的文章时,它大多只是在谈论DI是什么。因此,我得到了DI,但我试图了解每个人似乎都在使用的IoC容器的需求。IoC容器的目的真的只是为了“自动解决”依赖项的具体实现吗?也许我的类往往没有几个依赖关系,也许这就是为什么我认为没什么大不了的,但是我想确保我正确地理解了容器的效用。

我通常将我的业务逻辑分解为一个可能看起来像这样的类:

public class SomeBusinessOperation
{
    private readonly IDataRepository _repository;

    public SomeBusinessOperation(IDataRespository repository = null)
    {
        _repository = repository ?? new ConcreteRepository();
    }

    public SomeType Run(SomeRequestType request)
    {
        // do work...
        var results = _repository.GetThings(request);

        return results;
    }
}

因此,它仅具有一个依赖性,在某些情况下可能具有第二或第三依赖性,但并非经常如此。因此,任何调用此方法的都可以传递其自己的存储库,也可以允许其使用默认存储库。

就我对IoC容器的当前了解而言,该容器所做的只是解析IDataRepository。但是,如果这就是全部,那么我就没有看到太多的价值,因为当没有依赖项传入时,我的操作类已经定义了一个回退。所以我唯一想到的另一个好处是,如果我有多个操作,例如这个使用相同的后备仓库,我可以在注册表/工厂/容器的一个地方更改该仓库。那太好了,是吗?


1
通常,使用默认的回退版本的依赖关系实际上并没有任何意义。
Ben Aaronson

你是什​​么意思?“后备”是具体的类,除单元测试外,几乎所有时间都在使用。实际上,这将是在容器中注册的同一类。
Sinaesthetic

是的,但是使用了容器:(1)容器中的所有其他对象都具有的相同实例,ConcreteRepository并且(2)您可以提供其他依赖项ConcreteRepository(例如,数据库连接很常见)。
Jules

@Sinaesthetic我并不是说这总是一个坏主意,但通常是不合适的。例如,这将阻止您将洋葱体系结构与项目引用一起使用。也可能没有明确的默认实现。正如Jules所说,IOC容器不仅管理挑选依赖关系类型,而且还进行共享实例和生命周期管理之类的工作
Ben Aaronson 2014年

我将准备一件T恤,上面写着“功能参数-原始依赖项注入!”
格雷厄姆

Answers:


2

IoC容器与您只有一个依赖关系无关。这是关于您有3个依赖关系并且它们有几个依赖关系的情况,这些依赖关系具有依赖关系等

它还可以帮助您集中解决依赖关系,以及对依赖关系进行生命周期管理。


10

您可能要使用IoC容器有多种原因。

未引用的dll

您可以使用IoC容器从未引用的dll解析具体的类。这意味着您可以完全依赖抽象(即接口)。

避免使用 new

IoC容器意味着您可以完全删除new创建类的关键字。这有两个效果。首先是它使您的课程脱钩。第二个(相关的)是您可以放入模拟进行单元测试。这是非常有用的,尤其是在与长时间运行的流程进行交互时。

反对抽象写

使用IoC容器为您解决具体的依赖关系,就可以针对抽象编写代码,而不是根据需要实现所需的每个具体类。例如,您可能需要代码才能从数据库读取数据。无需编写数据库交互类,只需为它编写一个接口并对此进行编码。您可以在开发过程中使用模拟来测试正在开发的代码的功能,而不必在测试其他代码之前依赖开发具体的数据库交互类。

避免易碎的代码

使用IoC容器的另一个原因是,通过依赖IoC容器来解决依赖关系,可以避免在添加或删除依赖关系时更改对类构造函数的每个调用。IoC容器将自动解决您的依赖关系。一次创建一个班级,这不是一个大问题,但是在一百个地方创建班级时,这是一个巨大的问题。

终身管理和不受管理的资源清理

我要提到的最后一个原因是对象生存期的管理。IoC容器通常提供指定对象生存期的功能。在IoC容器中指定对象的生存期,而不是尝试在代码中手动管理它,这很有意义。手动生命周期管理可能非常困难。在处理需要处理的物体时,这很有用。某些IoC容器可以代替您手动管理对象的处置,而是为您管理处置,这可以帮助防止内存泄漏并简化代码库。

您提供的示例代码的问题在于,您正在编写的类对ConcreteRepository类有具体的依赖性。IoC容器将删除该依赖关系。


22
这些不是IoC容器的优点,而是依赖项注入的优点,可以通过穷人的DI轻松完成
Ben Aaronson 2014年

在没有IoC容器的情况下编写好的DI代码实际上可能非常困难。是的,优点有些重叠,但是这些都是使用IoC容器最好地利用的优点。
2014年

好吧,自我的评论以来您添加的最后两个原因更多地是针对容器的,并且在我看来都是非常有力的论点
Ben Aaronson 2014年

“避免使用新代码”-也阻碍了静态代码分析,因此您必须开始使用如下代码:hmemcpy.github.io/AgentMulder。您在本段中描述的其他好处是与DI有关,而不与IoC有关。而且,如果您避免使用new,但您的类仍将耦合,但将使用具体类型而不是接口作为参数。
2014年

1
总的来说,将IoC描绘为没有缺陷的东西,例如,没有提到很大的缺点:将一类错误推入运行时而不是编译时。
2014年

2

根据单一责任原则,每个班级都必须只有一个责任。创建类的新实例只是另一项责任,因此您必须将这种代码封装在一个或多个类中。您可以使用任何创建模式来执行此操作,例如工厂,构建器,DI容器等。

还有其他原理,例如控制反转和依赖反转。在这种情况下,它们与依赖关系的实例有关。他们声明必须将高级类与所使用的低级类(依赖项)分离。我们可以通过创建接口来使事物脱钩。因此,低层类必须实现特定的接口,而高层类必须利用实现这些接口的类的实例。(注意:REST统一接口约束在系统级别上应用相同的方法。)

通过示例将这些原则组合在一起(对低质量的代码感到抱歉,我不使用C#而是使用一些即席语言,因为我不知道这样):

  1. 没有SRP,没有IoC

    class SomeHighLevelService
    {
        public doFooBar(){
            Crap crap = doFoo();
            doBar(crap);
        }
    
        public Crap doFoo(){
            //...
            return crap;
        }
    
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  2. 更接近SRP,无IoC

    class SomeHighLevelService
    {
        public SomeHighLevelService(){
            Foo foo = new Foo();
            Bar bar = new Bar();
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  3. 是SRP,否IoC

    class HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new SomeHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new Foo();
        }
    
        private Bar getBar(){
            return new Bar();
        }
    }
    
    class SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    HighLevelServiceProvider provider = new HighLevelServiceProvider();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();
  4. 是SRP,是IoC

    interface HighLevelServiceProvider {
        SomeHighLevelService getSomeHighLevelService();
    }
    
    interface SomeHighLevelService {
        doFooBar();
    }
    
    interface Foo {
        Crap doFoo();
    }
    
    interface Bar {
        doBar(Crap crap);
    }
    
    
    class ConcreteHighLevelServiceContainer implements HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new ConcreteHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new ConcreteFoo();
        }
    
        private Bar getBar(){
            return new ConcreteBar();
        }
    }
    
    class ConcreteHighLevelService implements SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class ConcreteFoo implements Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class ConcreteBar implements Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    
    HighLevelServiceProvider provider = new ConcreteHighLevelServiceContainer();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();

因此,我们最终得到了一个代码,在其中您可以将每个具体实现替换为另一个实现相同c接口的实现。所以这很好,因为参与的类彼此分离,他们只知道接口。实例化代码可重复使用的另一个优点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.