依赖注入入门 - I






4.87/5 (67投票s)
什么是依赖注入,以及为什么需要这种软件设计模式。
简介
在这篇文章中,我将讨论我正在从事并探索的“依赖注入”。在这篇文章中,我将讨论什么是依赖注入,以及为什么需要这种软件设计模式。
依赖注入
为了更好地理解依赖注入,让我们看下面的代码,
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` 类来执行任务。这种代码是硬编码的依赖。
上面的图片显示了对象创建和使用的表示。
代码存在的问题是
- `NeedDependencyClass` 类本身负责创建自己的依赖。
- 代码是紧密耦合的,因为依赖无法替换。
- 代码也违反了 SOLID 原则的“开闭”原则,该原则规定“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭”。因为要修改 `Test` 方法,我需要修改 `DependencyClass`。
- 测试 `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 还可以实现的其他功能是:- 应用程序的并行编程
- 延迟绑定