IOC 容器 和 Dependency Injection parttern 阅读
原文 http://martinfowler.com/articles/injection.html 由令人崇拜的Martin Fowler 撰文
1.基本概念
IOC : Inversion of Control 控制反转
DI : Dependency Injection ,依赖注入
Service Locator ,服务定位器
而这一起的目标就是 : 将组件的配置与使用分离。
轻量级容器:用于组装各层组件。
组件:可以使用,配置,但不能修改的软件单元。
所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。
它们反转的是“如何定位插件的具体实现”:本来应该由使用插件的程序负责给插件初始化,填充需要的值,但是使用IOC,定位插件的具体实现的职责交由 插件自身来完成。
:而这些轻量级容器则使用了更为灵活的办法,只要插件遵循一定的规则,一个独立的组装模块就能够将插件的具体实现“注射”到应用程序中
2.演变
基本架构是这样的,MovieLister 和 MovieFinderImpl 已经解耦了,但是MovieLister还是 和Create 某个MovieFInder的 Impl 耦合,而如何解耦呢?
- 一般如果是不会发生变化的情况,我们经常会直接在MovieLister的构造函数中创建这个固定Impl,毕竟Over design 也不是好事儿。
- 如果MovieFinder的impl有多个的情况,那么一般是会创建一个Factory,为了更好的应对变化,通常还会配上一个Config 模块,这个我有写过。:)
我们当然希望 MovieLister 类只依赖于接口, 但我们要如何获得一个MovieFinder子类的实例呢?
在任何时候,我们总可以对使用组件的情形加以抽象,通过接口与具体的组件交流 (如果组件并没有设计一个接口,也可以通过适配器与之交流) 。但是,如果我们希望以不同的方式部署这个系统,就需要用插件机制来处理服务之间的交互过程,这样我们才可能在不同的部署方案中使用不同的实现。
MovieLister类负责定位 MovieFinder的具体实现——它直接实例化后者的一个子类。这样一来,MovieFinder 也就不成其为一个插件了,因为它并不是在运行期插入应用程序中的。而这些轻量级容器则使用了更为灵活的办法,只要插件遵循一定的规则,一个独立的组装模块就能够将插件的具体实现“注射”到应用程序中。
依赖注入的几种形式
Dependency Injection模式的基本思想是:用一个单独的对象(装配器)来获得MovieFinder的一个合适的实现,并将其实例赋给 MovieLister类的一个字段。
依赖注入的形式主要有三种,我分别将它们叫做构造子注入(Constructor Injection) 、设值方法注入(Setter Injection)和接口注入(Interface Injection)
Service Locator的类图
??看了这几个实例,那DI 和Factory 有什么区别???
DI | Factory | Named Entity | 依赖查询 | Service Locator | |
基本做法 | 用一个单独的对象(装配器)来获得MovieFinder的一个合适的实现,并将其实例赋给 MovieLister类的一个字段。 通常使用一个装配器类+配置文件。 但是具体的生成规则都是使用的xml. 配置文件中不只指明要使用哪一个实例,还包含初始化参数, MovieLister 依赖一个 MovieFinder实例,但是具体使用哪个在配置文件中指明,并且依赖的参数,也在配置文件中。 使用框架提供容器,注入配置信息,生成需要的实例。 应用程序代码都不依赖于服务接口的具体实现 把实例的创建,初始化都封住在xml中。 | 使用一个工厂类,封装创建产品的特定实例。 通常使用工厂类+配置文件, 但是配置文件中只是包含一个类的 FullName,或者包含预定义的枚举,并根据传入的参数生成对应实例。 应用程序依然依赖,接口的具体实现,还需要给这个实例进行赋值,等操作。 把创建实例的变化封装在工厂方法中。 经典 Factory Method 模式的问题在于:它们往往以静态方法的形式出现,你无法在接口中声明它们。你可以创建一个工厂类,但那又变成另一个服务实体了。 “工厂服务”是一种不错的技巧,但你仍然需要以某种方式实例化这个工厂对象,问题仍然没有解决。 | 使用一个独立的对象容器, | 有了依赖关系,才能谈依赖查询,而DI是不存在依赖关系的。 | 有一个对象(即服务定位器)知道如何获得一个应用程序所需的所有服务
通常需要一个Locator + 配置文件 Locator 知道每一个服务的创建方式,这里可以使用xml来创建这个服务。
DI使用一个创建方法,根据传入的对象名称(映射在xml中)去创建一个对象,所有的规则都在xml中指出。
而ServiceLocator,不是这样的,它每一个可以创建的服务,都会写成单独的创建方法,也就是它是固定的。但是更直观。 |
Service Locator vs. Dependency Injection
首先,我们面临Service Locator和 Dependency Injection之间的选择。应该注意,尽管我们前面那个简单的例子不足以表现出来, 实际上这两个模式都提供了基本的解耦合能力——无论使用哪个模式,应用程序代码都不依赖于服务接口的具体实现。两者之间最重要的区别在于:这个“具体实现”以什么方式提供给应用程序代码。使用 Service Locator 模式时,应用程序代码直接向服务定位器发送一个消息,明确要求服务的实现;使用 Dependency Injection模式时,应用程序代码不发出显式的请求,服务的实现自然会出现在应用程序代码中,这也就是所谓“控制反转” 。
一个关键的区别在于:使用 Service Locator 模式时,服务的使用者必须依赖于服务定位器。定位器可以隐藏使用者对服务具体实现的依赖,但你必须首先看到定位器本身。所以,问题的答案就很明朗了:选择 Service Locator还是 Dependency Injection,取决于“对定位器的依赖”是否会给你带来麻烦。
Dependency Injection 模式可以帮助你看清组件之间的依赖关系:你只需观察依赖注入的机制(例如构造子) ,就可以掌握整个依赖关系。而使用Service Locator模式时,你就必须在源代码中到处搜索对服务定位器的调用。具备全文检索能力的 IDE 可以略微简化这一工作,但还是不如直接观察构造子或者设值方法来得轻松。
使用 Dependency Injection模式,组件与注入器之间不会有依赖关系,因此组件无法从注入器那里获得更多的服务,只能获得配置信息中所提供的那些。这也是Dependency Injection模式的局限性之一。
带有参数的构造子可以明确地告诉你如何创建一个合法的对象。如果创建合法对象的方式不止一种,你还可以提供多个构造子,以说明不同的组合方式,这里是个题外话,微软出的编程规范,主张构造函数不要限制类的创建,而这里认为用构造函数直接创建合法的对象。其实我喜欢用构造函数创建合法对象。