65.9K
CodeProject 正在变化。 阅读更多。
Home

依赖注入入门 - I

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (67投票s)

2014 年 2 月 1 日

CPOL

5分钟阅读

viewsIcon

105779

什么是依赖注入,以及为什么需要这种软件设计模式。

简介  

在这篇文章中,我将讨论我正在从事并探索的“依赖注入”。在这篇文章中,我将讨论什么是依赖注入,以及为什么需要这种软件设计模式。  

依赖注入 

为了更好地理解依赖注入,让我们看下面的代码,

Public class client
{
     Public static void main()
     {
        NeedDependencyClass a = new NeedDependencyClass();
     }
}

public class NeedDependencyClass
{
  Private readonly DependencyClass _b;
  public NeedDependencyClass ()
  {
     _b = new DependencyClass();
  }
  public void Test()
  {
     _b.Test();
  }
}

public class DependencyClass
{
  public void Test()
  {
    // code for text method
  }
}
在上面的代码中,`client` 类创建了 `NeedDependencyClass` 并使用它,然后 `NeedDependencyClass` 类消耗 `DependencyClass` 类的服务来执行任务,即 `NeedDependencyClass` 类依赖于 `DependencyClass` 类来执行任务。这种代码是硬编码的依赖。
上面的图片显示了对象创建和使用的表示。


 

 

 

代码存在的问题是

  1. `NeedDependencyClass` 类本身负责创建自己的依赖。
  2. 代码是紧密耦合的,因为依赖无法替换。
  3. 代码也违反了 SOLID 原则的“开闭”原则,该原则规定“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭”。因为要修改 `Test` 方法,我需要修改 `DependencyClass`。
  4. 测试 `NeedDependencyClass` 类变得困难,因为我无法将 `DependencyClass` 类替换为其他依赖。
依赖注入是一种软件开发模式,它的出现是为了解决上述问题。

该模式的定义是:移除硬编码的依赖,并使其能够在运行时或编译时进行替换。

在下一节中,我将描述如何通过依赖注入模式解决上述问题。

解决方案

1) 让客户端(即依赖图中第一个消耗服务的类)创建所有依赖对象
对上面代码的第一个问题的解决方案是,将提供所有必需依赖的责任交给依赖图中的第一个对象,即客户端类,它是依赖图中的第一个类。

所以代码会像这样改变
Public class client
{
     Public static void main()
     {
        NeedDependencyClass a = new NeedDependencyClass(new DependencyClass());
      }
}

public class NeedDependencyClass
{
  Private readonly DependencyClass _b;
  public NeedDependencyClass(DependencyClass b)
  {
     _b = b;
  }
  public void Test()
  {
     _b.Test();
  }
}

public class DependencyClass
{
  public void Test()
  {
    // code for text method
  }
}
下面的图片显示对象创建仅由客户端类对象完成,依赖链中的其他类使用提供的依赖对象。



2) 消除对象之间的紧密耦合,即允许定义松耦合
为了更好地理解第二个问题,让我们以计算机或笔记本电脑的真实生活场景为例。
 

 

正如你在上面的图片中看到的,我们为每个外部设备都有端口,我可以连接外部设备并在那里工作。

但问题在于,我不能将键盘连接到打印机端口,反之亦然,其他设备也存在同样的问题。所以这就好比是紧密耦合,我不能在给定的接口上(即我所依赖的接口)更换我的外部设备。

解决方案是 USB 端口。

如果我有 USB 端口,我就可以轻松地将任何设备连接到我的机器并执行我的任务。



类之间的紧密耦合是什么?(在编程中)

让我们从一个简单的例子开始
public class NeedDependencyClass
{
  Private readonly DependencyClass _b;
  public NeedDependencyClass(DependencyClass b)
  {
     _b = b;
  }
  public void Test()
  {
     _b.Test();
  }
}

public class DependencyClass
{
  public void Test()
  {
    // code for text method
  }
}

现在,如你所见,在上面的代码中,`NeedDependencyClass` 类利用了 `DependencyClass` 类,这意味着 `NeedDependencyClass` 类依赖于 `DependencyClass` 类。这种依赖是 `NeedDependencyClass` 类和 `DependencyClass` 类之间的一种紧密依赖。因为 `DependencyClass` 类作为参数传递给了 `NeedDependencyClass` 类,并且 `NeedDependencyClass` 类利用了 `DependencyClass` 类的某个方法。现在,为什么称之为紧密耦合。如果代码像这样创建
NeedDependencyClass a = new NeedDependencyClass(new DependencyClass()); 
现在,如果我像下面这样创建 `DependencyClass1` 类,public
class DependencyClass1 { 
      public void Test() 
      { //code for text method } 
}
我想在类中传递 `DependencyClass1` 对象而不是 `DependencyClass` 对象并调用 test 方法
NeedDependencyClass a = new NeedDependencyClass(new DependencyClass1()); 
这会产生编译器错误,因为我试图将 `DependencyClass1` 对象作为参数传递。这意味着 `NeedDependencyClass` 类无法接受除 `DependencyClass` 类以外的任何其他类。

解决这个问题的方法是定义一个接口,如下所示,由类实现
Interface IDependencyClass
{
   Void Test();
}

Class DependencyClass : IDependencyClass
{
 ……
}
Class DependencyClass1 : IDependencyClass
{
 ……
}
所以代码会像这样改变
public class NeedDependencyClass
{
  Private readonly IDependencyClass _b;
  public NeedDependencyClass(IDependencyClass b)
  {
     _b = b;
  }
  public void Test()
  {
     _b.Test();
  }
}
这段代码无论是 `DependencyClass` 类还是 `DependencyClass1` 类都能正常工作。

下面的图片显示 `DependencyClass` 对象仅由客户端类对象创建,依赖链中的其他类使用提供的依赖对象。而 `NeedDependencyClass` 类依赖于 `IDependencyClass` 接口,该接口可以被任何新的 `DependencyClass` 类实现和替换。

3) 让你的类依赖于抽象
现在有一个需求,即只有经过身份验证的应用程序用户才能运行应用程序中的 `Test` 方法,那么类的开发者需要像这样修改 `Test` 方法:
public class DependencyClass : IDependencyClass
{
  public void Test()
  {
    using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, 
        "YOURDOMAIN"))
 {
      bool isValid = pc.ValidateCredentials("myuser", 
       "mypassword");
               if(isValid)
         Console.Writeln(“Testing method for the data”);
               
 } 
  }
}
这违反了 SOLID 原则中的“开闭”原则,该原则规定“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭”。

因此,为了避免这种情况,需要像为了解决紧密耦合问题那样,利用抽象,即创建接口,以便可以轻松地进行类扩展。

以下是如何扩展代码,使其不违反“开闭”原则。
Class DependencyClass : IDependencyClass
{
 public void Test()
  {
    Console.Writeln(“Testing method for the data”);
  }
}

Class SecureDependencyClass : IDependencyClass
{
  IDependencyClass _b; 
  public SecureDependencyClass(IDependencyClass b)
  {
    _b = b;
  }
  public void Test()
  {
    using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, 
        "YOURDOMAIN"))
     {
         bool isValid = pc.ValidateCredentials("myuser", 
                                         "mypassword");
         if(isValid)
            this._b.Test();   
               
     } 
  }
}
正如你在上面的代码中看到的,创建了一个名为 `SecureDependencyClass` 的新类。这个类扩展了前一个类的功能,并且没有违反“开闭”原则。

这是通过将现有的 `DependencyClass` 类注入到新类 `SecureDependencyClass` 中实现的,这也是依赖注入的一个例子。因此 `SecureDependencyClass` 消耗 `DependencyClass` 依赖。

并且消费者客户端代码需要像下面这样修改
Public class client
{
     Public static void main()
     {
        IDependencyClass dependency = new SecureDependencyClass(new 
       DependencyClass());
        NeedDependencyClass a = new NeedDependencyClass(dependency);
      }
}

public class NeedDependencyClass
{
  Private readonly IDependencyClass _b;
  public NeedDependencyClass(IDependencyClass b)
  {
     _b = b;
  }
  public void Test()
  {
     _b.Test();
  }
}
下面的图片显示 `SecureDependencyClass` 对象仅由客户端类对象创建,依赖链中的其他类使用提供的依赖对象。而 `NeedDependencyClass` 类依赖于 `IDependencyClass` 接口,该接口可以被任何新的 `SecureDependencyClass` 类实现和替换。


这还表明 `NeedDependencyClass` 不依赖于特定的类,而是依赖于抽象。

4) 使类可测试
现在,一旦完成了第二个步骤,`NeedDependencyClass` 类就变得易于测试了。因为原始代码的问题在于硬编码的依赖,无法替换。

例如,以下是最终代码
public class NeedDependencyClass
{
  Private readonly IB _b;
  public NeedDependencyClass(IB b)
  {
     _b = b;
  }
  public int Test()
  {
     var c=_b.Test();
     return c*10;
  }
}

public class DependencyClass :IDependencyClass
{
  public void Test()
  {
    // code for getting value form database
    using(SqlConnection c = new SqlConnection(connectionstring))
    {
     c.open(); 
        SqlDataReader myReader = null;
SqlCommand    myCommand = new SqlCommand("select count(*) from table", c);
        return (Int32) cmd.ExecuteScalar();
  }
}
根据代码,`DependencyClass` 类的 `Test` 方法与 SQL Server 建立连接并返回值。

但是,为了测试 `NeedDependencyClass` 类,没有必要执行昂贵的数据库连接。所以我们可以用 `TestDependencyClass` 类替换 `DependencyClass` 类,如下所示。
public class NeedDependencyClass
{
  Private readonly IDependencyClass _b;
  public NeedDependencyClass(IDependencyClass b)
  {
     _b = b;
  }
  public int Test()
  {
     var c=_b.Test();
     return c*10;
  }
}

public class TestDependencyClass :IDependencyClass
{
  public void Test()
  {
     return 10;
  }
}

Public class TestClient
{
     Public static void main()
     {
NeedDependencyClass a = new NeedDependencyClass(new TestDependencyClass());
      }
}
 

结论 

因此,依赖注入是一种重要的设计模式,它允许开发可维护的代码,并且满足 SOLID 原则。DI 不仅有助于解决上述问题,我们还可以实现以下目标,DI 还可以实现的其他功能是:
  1. 应用程序的并行编程
  2. 延迟绑定   
不要忘记发表你的评论。 
© . All rights reserved.