控制反转和依赖注入

时间:2012-01-09 09:47:30

标签: design-patterns architecture dependency-injection inversion-of-control

这是一个非常着名的概念:Ioc(控制反转)。 我已经使用这个概念很长一段时间了。通常我会使用DI(而不是服务定位器)方法在我的代码中实现IoC。 以下是我对IoC的理解。

如果classA在其中声明了classB的实例,则它依赖于classB。它依赖于classB,由于某种原因(我稍后会谈到),不好,因此我们使用DI来解决这个问题。所以现在我们有一个名为IClassB的接口,它将在classA的构造函数中传递(我在这里采用构造函数注入,为了说明)。这是代码:

public class ClassA
{
    IClassB _classBObj = null;

    public ClassA(IClassB classBObj)
    {
         _classBObj = classBObj;
    }

    public void UseClassBMethod()
    {
        this._classBObj.classBMethod();
    }
}

public class ClassB:IClassB
{

    public void classBMethod()
    {
        //Some task carried out by classB.
    }
}

public interface IClassB
{
    void classBMethod();
}

这里有使其运行的代码:

class Program
{
    static void Main(string[] args)
    {
        //This code is outside of class A and hence declaring 
        //object of ClassB is not dependency
        ClassA classA=new ClassA(new ClassB);
        classA.UseClassBMethod();

    }
}

我已经演示了上述示例,以确保我对IoC和DI的理解是否正确。如果您发现任何错误,请纠正我。 现在,当我试图找出IoC的原因时,我发现了两个重要原因:

  1. 可测试性:当然是一个正确的问题。让我们假设,在上面的例子中,例如,classB方法使用SMPTP服务器或某些WCF / Webservice,我们可能无法测试它。但是,我们可以创建一个实现接口IClassB的测试存根类,并通过传递测试存根类的实例继续进行测试。

  2. 具体实施方面的依赖性:这是我无法与之相处的。即使我使用以下代码更改了classA的代码:

  3. public class ClassA
    {
       ClassB _classBObj = null;
    
       public ClassA()
       {
         _classBObj = new ClassB();
       }
    
       public void UseClassBMethod()
       {
          this._classBObj.classBMethod();
       }
    }
    

    IoC如何帮助摆脱因ClassB对象的classB方法发生变化而引起的任何问题?如果方法签名有任何变化,我们也必须在接口IClassB的方法签名中进行更改(事实上,这正在增加重构任务。)。如果方法实现更改说例如它需要额外的日志记录任务,那么更改将仅限于ClassB代码。

    我可能对这个问题看起来有点愚蠢,但有人会提出一个场景,除了单元可测性之外,我们会从这种方法中受益吗?

    非常感谢您阅读本文。

2 个答案:

答案 0 :(得分:6)

这里的核心概念是program against an interface 您的代码不知道任何具体的类。

因此,您可以在不影响代码的情况下切换具体实现。

在您的情况下,您可以将ClassA classA=new ClassA(new ClassB);替换为ClassA classA=new ClassA(new ClassC); 不同的行为,是更少或更优或需要一些密码来做某事等。

我们的想法是,如果ClassC遵循ClassB也实现的界面,那么您可以更改为使用ClassC 您的代码中的任何更改

如果界面发生变化,您的代码当然会受到影响。这是你无法避免的。

但您获得的是,您可以在不影响代码的情况下切换和更改具体实现,并且这种更改比根据您的需要正确定义的界面更频繁地需要和发生

答案 1 :(得分:2)

我认为在某些情况下,您不希望将实例注入构造函数。我经常注入懒惰的对象或工厂代表。我会尽快解释你为什么要这样做。

在某些现实场景中,您最终可能会在构造函数中注入大量实例,有时根据应用程序逻辑,在对象的生命周期中只有少数几个实际使用。这意味着有时即使未使用依赖关系也会初始化依赖关系。

在.NET 4中,您可以使用Lazy类,它允许您仅在首次使用时才实例化依赖项类。在实例化依赖关系需要很长时间或实例需要大量内存时很酷。

public class ClassA
{
    private readonly Lazy<IClassB> _lazyClassBObj;

    public ClassA(Lazy<IClassB> lazyClassBObj)
    {
      _lazyClassBObj = lazyClassBObj;        
    }

    public void UseClassBMethod()
    {
        _lazyClassBObj.Value.classBMethod();
    }
}

class Program
{
    static void Main(string[] args)
    {
        ClassA classA = new ClassA (new Lazy<IClassB>(() => new ClassB));
        ...

    }
}

另一个技巧是将工厂委托注入构造函数而不是实例。它与Lazy解决方案非常相似,但它有一些优点。如果您的类必须能够创建任意数量的依赖类的新实例(想要在循环中创建实例或类似的东西),则注入工厂委托很酷。在此示例中,ClassA将引用一个方法,该方法可以创建实现IClassB的对象。为了使事情变得更有趣,实现IClassB的ClassB也具有IClassC依赖性。

public class ClassA
{
    private readonly Lazy<IClassC> _lazyClassBObj;
    private readonly Func<IClassC, IClassB> _classBObjFactory;

    public ClassA(Func<IClassC, IClassB> classBObjFactory, Lazy<IClassC> lazyClassBObj)
    {
      _classBObjFactory = classBObjFactory;
      _lazyClassBObj = lazyClassBObj;        
    }

    public void UseClassBMethod()
    {
        var classC = _lazyClassBObj.Value;

        var classB1 = _classBObjFactory(classC); // instance 1
        var classB2 = _classBObjFactory(classC); // instance 2
        var classB3 = _classBObjFactory(classC); // instance 3
    }
}

此方法的主要好处是通过不初始化您可能不使用的对象来减少内存需求。工厂委托注入的好处是您可以隐藏依赖项的init逻辑,并且可以控制向调用者公开哪些工厂参数。 ClassA不需要知道如何组装它的所有依赖项,它只需要知道它知道的params并且它可以控制。它允许您更轻松地替换依赖项。

我希望它有意义:)只是想演示如何使用C#功能样式从模式中获得更多。