依赖注入

大佬MartinFowler 文章:

英文版:Inversion of Control Containers and the Dependency Injection pattern

中文版:IoC容器和Dependency Injection模式

 

我的摘抄:

摘要:

DI(Dependency Injection)依赖注入作为IOC(控制反转)更能描述其特点的名字。最重要的是将组件的使用和配置分离

问题:如果web控制器体系结构和数据库接口是由不同的团队所开发的,彼此几乎一无所知,应该如何让它们配合工作?

通过 "组装各层组件" 的方案解决问题,这些框架称为“轻量级容器”如PicoContainer和Spring。

组件和服务

组件:使用者不能对其源代码进行修改,但可以通过作者预留的某些途径进行扩展,以改变组件的行为。

两者共同点:都被外部应用使用。

差异:组件通过本地使用(Jar包、dll、源码导入);服务通过同步或者异步远程接口远程使用(WebService、消息系统、RPC、或者Socket).

在一个真实的系统中,我们可能有数十个服务和组件。在任何时候,我们总可以对使用组件的情形加以抽象,通过接口与具体的组件交流(如果组件并没有设计一个接口,也可以通过适配器与之交流)。

如果我们希望以不同的方式部署这个系统,就需要用插件机制来处理服务之间的交互过程,这样我们才可能在不同的部署方案中使用不同的实现。

索引现在的核心问题:何将这些插件组合成一个应用程序?这正是新生的轻量级容器所面临的主要问题,而它们解决这个问题的手段无一例外地是控制反转(Inversion of Control)模式。

控制反转

MovieLister类中直接实例化(new)一个MovieFinderImpl对象这样MovieFinderImpl对象不能称为为插件,它不再运行时插入应用程序

容器使用更灵活的方法,只要插件遵循一定的规则,一个独立的组装模块能够将插件的具体实现注射到应用程序中。“控制反转”作为这个模式名字容易让人疑惑,起名为“依赖注入”。

依赖注入的几种形式

DI模式基本思想:用一个单独的对象(装配器)来获得MovieFinder的一个合适的实现,并将其实例赋给MovieLister类的一个字段。

注入的 三种形式:

1.构造方法注入(Constructor Injection)

2.设值注入(setter Injection)

3.接口注入 (Interface Injection)

Dependency Injection模式的基本思想是:用一个单独的对象(装配器)来获得类(MovieFinderImpl)的一个合适的实现,并将其实例赋给使用类(MovieLister)的一个字段。

 

Service Locator

另一种打破层依赖关系的手段

有一个对象(即服务定位器)知道如何获得一个应用程序所需的所有服务。也就是说,在我们的例子中,服务定位器应该有一个方法,用于获得一个MovieFinder实例。

 

选择 Service Locator vs. Dependency Injection

共同点:两个模式都提供了基本的解耦合能力。无论使用哪个模式,应用程序代码都不依赖于服务接口的具体实现。

区别:具体实现以什么方式提供给应用程序代码。

           使用Service Locator模式时,应用程序代码直接向服务定位器发送一个消息,明确要求服务的实现,它依赖于服务定位器,定位器隐藏服务的具体实现。

          使用Dependency Injection模式时,应用程序代码不发出显式的请求,服务的实现自然会出现在应用程序代码中,这也就是所谓控制反转。

如何选择的这两种方式关键问题在于他们的区别

使用Service Locator模式时,服务的使用者必须依赖于服务定位器。定位器可以隐藏使用者对服务具体实现的依赖,但你必须首先看到定位器本身。所以,问题的答案就很明朗了:选择Service Locator还是Dependency Injection,取决于对定位器的依赖是否会给你带来麻烦。

用插件(plugin)机制取代重量级组件会对测试过程有很大帮助,这正是测试驱动开发(Test Driven Development,TDD)之类实践的关键所在。???

选择 构造函数注入 vs. 设值方法注入

设值函数注入和构造函数注入之间的选择相当有趣,因为它折射出面向对象编程的一些更普遍的问题:应该在哪里填充对象的字段,构造函数还是设值方法?

首选的做法是尽量在构造阶段就创建完整、合法的对象,带有参数的构造函数可以明确地告诉你如何创建一个合法的对象。如果创建合法对象的方式不止一种,你还可以提供多个构造函数,以说明不同的组合方式。

但是如果参数太多,构造函数会显得凌乱不堪,如果有不止一种的方式可以构造一个合法的对象,也很难通过构造函数描述这一信息,因为构造函数之间只能通过参数的个数和类型加以区分。

对象有多个构造函数,对象之间又存在继承关系,事情就会变得特别讨厌。为了让所有东西都正确地初始化,你必须将对子类构造函数的调用转发给超类的构造函数,然后处理自己的参数。这可能造成构造函数规模的进一步膨胀。

尽管有这些缺陷,但我仍然建议你首先考虑构造函数注入。不过,一旦前面提到的问题真的成了问题,你就应该准备转为使用设值方法注入。

选择 代码配置 vs. 配置文件

对于大多数需要在多处部署的应用程序来说,一个单独的配置文件会更合适。

可以编写构造器完成装配工作,需要多种构造器可以通过配置文件来选择。

分离配置与使用

服务的配置应该与使用分开。实际上,这是一个基本的设计原则——分离接口与实现。

你是否希望将选择具体实现类的决策推迟到部署阶段。如果是,那么你需要使用插入技术。使用了插入技术之后,插件的装配原则上是与应用程序的其余部分分开的,这样你就可以轻松地针对不同的部署替换不同的配置。

结论和思考

在时下流行的轻量级容器都使用了一个共同的模式来组装应用程序所需的服务,我把这个模式称为Dependency Injection,它可以有效地替代Service Locator模式。在开发应用程序时,两者不相上下,但我认为Service Locator模式略有优势,因为它的行为方式更为直观。但是,如果你开发的组件要交给多个应用程序去使用,那么Dependency Injection模式会是更好的选择。

如果你决定使用Dependency Injection模式,这里还有几种不同的风格可供选择。我建议你首先考虑构造函数注入;如果遇到了某些特定的问题,再改用设值方法注入。如果你要选择一个容器,在其之上进行开发,我建议你选择同时支持这两种注入方式的容器。

Service Locator 模式和Dependency Injection 模式之间的选择并是最重要的,更重要的是:应该将服务的配置和应用程序内部对服务的使用分离开

 

工具:

AutoFac

Castle

posted @ 2018-05-31 16:23  vvf  阅读(134)  评论(0)    收藏  举报