设计模式 –控制反转和依赖注入






4.78/5 (135投票s)
设计模式 –
更新
添加了 MVC 和 MVP 设计模式教程,日期:2008 年 12 月 19 日
目录
更新时间
添加了关于如何使用 Unity 应用程序块进行 DI 的链接。
引言
您可以通过以下链接阅读我之前关于设计模式、UML、MVC 和 MVP 的文章
- 使用 Unity 应用程序块进行 DI https://codeproject.org.cn/KB/aspnet/IOCandDI.aspx
- 第一部分 – 设计模式:工厂、抽象工厂、建造者、原型、浅拷贝和深拷贝,以及单例和命令模式
- 第二部分 – 设计模式:解释器、迭代器、中介者、备忘录和观察者模式
- 第三部分 – 设计模式:状态、策略、访问者、适配器和享元模式
- 第四部分 - 设计模式:桥梁、组合、装饰器、外观、COR、代理和模板模式
- 第五部分 模型-视图-控制器 MVC
- 第六部分 模型-视图-呈现器
您可以在此处下载我的架构面试题书。
在本节中,我们将讨论 IOC 和 DI 如何帮助我们构建松耦合的软件架构。我不确定我们是否应该称其为设计模式,还是更像一种方法。如果您在网上搜索,会发现关于 IOC 是否是设计模式有很多争议。从我的角度来看,它是一种设计模式,因为它解决了特定的问题情境。
很高兴看到实际的架构使用面向容器的方法实现 IOC。我相信这将改变我们对组件之间交互方式的看法。
那么,让我们详细了解 IOC 和 DI。
问题——紧耦合
在我们深入了解 IOC 和 DIP 的缩写之前,让我们先了解问题。考虑下面的例子:我们有一个客户类,其中包含一个地址类的对象。代码中最大的问题是类之间的紧耦合。换句话说,客户类依赖于地址对象。因此,如果地址类发生任何更改,也会导致‘ClsCustomer
’类的更改和重新编译。因此,让我们列出这种方法的缺点:
- 最大的问题是客户类控制了地址对象的创建。
- 地址类在客户类中被直接引用,这导致地址和客户对象之间存在紧耦合。
- 客户类知道地址类的类型。因此,如果我们添加新的地址类型,如家庭地址、办公室地址,客户类也需要进行更改,因为客户类暴露于实际的地址实现。
图:- IOC 的问题
因此,如果由于任何原因地址对象无法创建,整个客户类将在构造函数初始化时就失败。
解决方案
现在我们知道了问题,让我们来了解解决方案。解决方案肯定围绕着将对象创建的控制权从客户类转移到其他人。主要问题源于客户类创建地址对象。如果我们能将这个任务/对象创建的控制权从客户类转移到另一个实体,我们就解决了问题。换句话说,如果我们能将这种控制权反转给第三方,我们就找到了解决方案。所以解决方案的名称是 IOC(控制反转)。
IOC 原理
IOC 的基本原理建立在好莱坞原则的基础上(好莱坞业余演员试镜时得到的答复)
不要给我们打电话,我们会给你打电话
翻译成宝莱坞(针对挣扎中的演员)
Aap Mauke ko mat bulao, mauka aap ke paas ayega – 印地语转换?
换句话说,就像地址类对客户类说,不要创建我,我会用别人来创建我自己。
IOC 有两个原则
- 主类聚合其他类时不应依赖于聚合类的直接实现。这两个类都应该依赖于抽象。因此,客户类不应直接依赖于地址类。地址类和客户类都应依赖于抽象,无论是通过接口还是抽象类。
- 抽象不应依赖于细节,细节应依赖于抽象。
图:- IOC 框架
图‘IOC 框架’展示了我们如何实现这种解耦。最简单的方法是公开一个允许我们设置对象的方法。让 IOC 框架委托地址对象的创建。IOC 框架可以是一个类、客户端或某种 IOC 容器。因此,它将是一个两步过程:IOC 框架创建地址对象,然后将此引用传递给客户类。
实现 IOC 的方式
好的,现在我们知道了问题,让我们尝试了解更广泛的解决方案。让我们看看我们如何实现 IOC 的解决方案。IOC 是使用 DI(依赖注入)实现的。我们在前面的章节中广泛讨论了如何注入依赖项。在本节中,我们将更深入地探讨实现 DI 的其他方法。
图:- IOC 和 DI
图‘IOC 和 DI’展示了 IOC 和 DI 的组织方式。因此,我们可以说 IOC 是一个原则,而 DI 是实现 IOC 的一种方式。在 DI 中,我们有四种更广泛的实现方式:
- 构造函数方式
- 暴露 set 和 get 方法
- 接口实现
- 服务定位器
在后续章节中,我们将更详细地介绍。
构造函数方法
在这种方法中,我们在构造函数本身中传递对象引用。因此,当客户端创建对象时,它在创建对象时在构造函数中传递对象。这种方法不适用于只能使用默认构造函数的客户端。
图:- 基于构造函数的 DI
Setter 和 Getter
这是最常用的 DI 方法。依赖对象通过类的 set/get 方法暴露。缺点是由于对象是公开暴露的,它违反了面向对象编程的封装规则。
图:- Getter 和 Setter
基于接口的 DI
在这种方法中,我们实现了 IOC 框架的一个接口。IOC 框架将使用接口方法将对象注入到主类中。您可以在图‘基于接口的 DI’中看到,我们实现了一个名为‘IAddressDI
’的接口,该接口有一个‘setAddress
’方法,用于设置地址对象。然后,客户类实现了该接口。外部客户端/容器随后可以使用‘setAddress
’方法将地址对象注入到客户对象中。
图:- 基于接口的 DI
服务定位器
注入依赖项的另一种方法是使用服务定位器。聚合子对象的主类将使用服务定位器来获取地址对象的实例。服务定位器类不创建地址对象的实例,它提供了一种注册和查找有助于创建对象服务的机制。
图:- 服务定位器
实现 DI
现在我们知道了实现 IOC 的各种 DI 类型。是时候了解我们如何实际实现这些 DI 了。
DI FACTORY 有什么问题?
首先想到的是,我们不能使用工厂实现所有这些吗?主要问题在于一个类负责创建其包含对象的创建活动,这导致了严重的耦合。引入工厂可以在很大程度上解决这个问题。
以下是工厂存在的问题,迫使我们考虑其他解决方案:
- 一切都是硬编码的:- 工厂最大的问题是它无法在应用程序之间重用。所有选项都硬编码在工厂本身中,这使得工厂对特定实现非常严格。
- 依赖于接口:- 工厂所基于的是通用接口。接口将实现与对象创建过程解耦。但然后所有类都应实现一个通用接口。这本身又是一个限制。
- 工厂是自定义的:- 它们非常适合特定实现。
- 一切都是编译时:- 工厂中对象的任何依赖对象都必须在编译时已知。
容器方式
容器是负责对象管理、实例化和配置的抽象。因此,您可以使用容器来配置对象,而不是编写像工厂模式这样的客户端代码来管理对象。有许多容器可用,可以帮助我们轻松管理依赖注入。因此,而不是编写大量的工厂代码,容器会识别对象依赖项,然后创建并将它们注入到适当的对象中。
图:- 运行中的容器
所以,您可以将容器想象成一个中间人,它将地址和客户对象注册为单独的实体,然后容器创建客户和地址对象,并将地址对象注入到客户中。因此,您可以直观地看到容器提供的高级别抽象。
我们将使用 Windsor 容器之一来覆盖客户和地址的示例,您可以在此处获取有关该容器的更多详细信息。
使用 Windsor 实现
我们首先创建地址接口,然后从该接口创建具体类。接口将是用于注入的实体,而不是具体对象,因此我们处理的是抽象而非具体实现。
图:- 地址接口
在客户类中,我们通过构造函数传递了对象。
图:- 客户类
如果让我们编写客户端代码。它将类似于图‘客户端代码’所示。在步骤 1 中,我们创建一个具体对象并将实现指向 IAddress 接口。在步骤 2 中,我们在创建对象时将接口对象传递给客户类的构造函数。
图:- 客户端代码
好的,现在让我们看看如果使用 Windsor 容器,这将会如何工作。图‘Windsor 容器’展示了它的外观。因此,步骤 1 创建 Windsor 容器对象。步骤 2 和 3 在容器中注册类型和具体对象。步骤 4 请求容器创建客户对象。在此步骤中,容器解析并将地址对象设置到构造函数中。步骤 5 释放客户对象。
图:- Windsor 容器
好的,大家明白了,上面的代码比客户端代码复杂。在实际使用容器的实现中,我们从不使用客户端代码,而是使用配置文件。您可以从图‘使用配置文件创建’中看到,我们拥有更好的灵活性来添加更多对象。XmlInterpreter 对象有助于读取配置文件以在容器中注册对象。通过 container.resolve 方法,我们最终创建了客户对象。因此,容器充当了理解客户对象,然后通过构造函数将地址对象注入到客户对象中的中介角色。在配置文件中,我们需要在 components 部分定义所有组件。
图:- 使用配置文件创建
参考文献
Hanselman 提供了一个容器列表,是很有用的链接
- http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx
- http://msdn.microsoft.com/en-us/magazine/cc163739.aspx
- http://msdn.microsoft.com/en-us/library/cc707905.aspx
- http://www.devx.com/Java/Article/27583/0/page/2
进一步阅读,请观看下面的面试准备视频和分步视频系列。