依赖注入Dependency Injection模式的特点分析与实现

依赖注入(Dependency Injection)模式的特点分析与实现
作者: 来源:BlogJava  发布时间:2006-03-14 00:00:00.0

依赖注入(Dependency Injection模式的特点分析与实现

――构造子注入Constructor Injection模式的分析与实现



超越巅峰 2006-03-14 08:52

chaocai2001@yahoo.com.cn

摘要:本文对IoC模式、依赖注入(Dependency Injection) 模式做了简要介绍,文中分析构造子注入模式与其他模式相比较的优势和特点,并给出了在JAVA中实现该模式的方法。

1 引言

IoCInversion of Control)模式以被目前的轻量级容器所广泛应用,通过IoC模式这些容器帮助开发者将来自不同项目的组件装配成一个内聚的应用程序。轻量级的IoC容器(如Springpico-container)虽然为我们的开发提供了很大的便利,但是在很多情况下这些轻量级容器所提供的功能并不一定非常适合我们的需要,也许这些容器的功能过于庞大了,或者所提供的功能缺乏对特定应用的针对性,或者我们需要更高的运行效率,这时我们可以在了解IoC的原理的基础上利用JAVA的反射机制自己实现灵活的、可扩展的组件机制。

2 IoC与依赖注入(Dependency Injection)模式简介

GoF的设计模式相同,IoC模式同样是关注重用性,但与GoF模式不同的是IoC模式更加关注二进制级的重用性和可扩展性,即可以直接通过二进制级进行扩充,复用的模块通常被称为组件或者插件,组件和插件都是在运行时进行装载的。

GoF的设计模式中我们大量看到的是面向接口编程:Interface Driven Design 接口驱动,接口驱动有很多好处,可以提供不同灵活的子类实现,增加代码稳定和健壮性等等,但是接口一定是需要实现的,也就是如下语句迟早要执行:

AInterface a = new AInterfaceImp();

由于以上的代码被写入了调用者程序中,同时象AinterfaceImp这样的接口的实现类是在编译时被装载的,如果以后想加入新的接口实现类则必须修改调用者的代码。

IoC模式与以上情况不同,接口的实现类是在运行时被装载的,这样即使以后新添加了接口实现类是也不需修改调用者的代码(可以通过特定的方式来定位新增的实现类,如配置文件指定)。IoC英文为 Inversion of Control,即反转模式,这里有著名的好莱坞理论:你呆着别动,到时我会找你。

IoC模式可以延缓接口的实现,根据需要实现,有个比喻:接口如同空的模型套,在必要时,需要向模型套注射石膏,这样才能成为一个模型实体,因此,对于这些新生的容器,它们反转的是“如何定位插件的具体实现”。因此, Martin Fowler 给这种模式起了一个形象的名称“依赖注入”Dependency Injection

图表 1 采用Dependency Injection前后的依赖关系变化

依赖注入的形式主要有三种,分别将它们叫做构造子注入(Constructor Injection)、设值方法注入(Setter Injection)和接口注入(Interface Injection)。

这三种方式在Martin Fowler的《Inversion of Control Containers and the Dependency Injection pattern》中都给出了详细的定义及说明,本文就不再赘述了,下面的内容将着重介绍构造子注入模式的特点及实现方法。

3 构造子注入模式的特点及实现

3.1 构造子注入模式的特点

通常情况下设方法注入和接口注入较易于被开发人员接受,而构造子注入则应用较少,实际上构造子注入具有很多其他两者所不具有的优势:

1 构造子注入形成了一种更强的依赖契约

2 可以获得更加简明的代码

3 更加简明的依赖声明机制,无须定义XML配置文件或设方法

4 更加符合接口与实现分离的组件特征,组件接口表明能够向其它组件提供的服务,而实现则应该是所提供服务的实现应该与服务契约无关(即不应包含用于获得依赖的设值方法等)。

5 不会出现不确定的状态。在设值方法注入中,由于并不是所有的设值方法(setter)都一定会被调用的,所以会有不确定状态。

从以上几点我们还可以分析出构造子注入对于组件代码的入侵性远小于其它两种模式(接口注入使得组件必须实现特定接口,设值方法同样要求组件提供特定的setter方法),代码更加易于维护

图表 2 示例中类的关系

Client的实现依赖于接口ABC的实现,但是为了提供系统更好的灵活性和可扩展性,各接口的实现以组件的方式利用java的反射机制进行运行时装载,注意到组件间可能会存在某种依赖关系,例如组件AX依赖与接口B的实现类,而这中依赖关系必须在运行时动态注入,组件为了告诉组件的调用者这种依赖关系以便注入,可以使用上文提到的各种模式:

1 使用接口注入模式

public interface InjectB{

public void injectB(B bImp);

}

public interface InjectC{

public void injectC(C cImp);

}

public class AImp implements A,InjectB,InjectC{

public void injectB(B bImp);

public void injectC(C cImp);

}

2 使用设值注入模式

public class AImp implements A {

public void setB(B bImp);

public void setC(C cImp);

}

3 使用构造子注入模式

public class AImp implements A {

public AImp(B bImp,C cImp){

}

}

由以上实例可以清楚的看出采用构造子注入模式的实现组件代码最为简单,且所受的入侵性最小。

3.2 JAVA中实现构造子注入模式

java.NET这样具有反射功能的语言中实现类型的运行时载入并不复杂,只要通过Class.forName或生成自己的ClassLoader就可以实现。

同样我们可以通过反射机制获取组件构造函数的参数,注入相应接口的实现,作者将此过程进行了封装,以下是代码:

public class RefectHelper {

public Object ConstructorHelper(String className,ConstructorParamDeal pd) throws Exception{

try{

//获取类中的构造函数

Constructor[] constructs=Class.forName(className).getConstructors(); //实现中默认使用第一个构造函数类创建实例

Class [] classes=constructs[0].getParameterTypes();

//获取要注入的参数实例

Object []obj=pd.dealParam(classes);

//创建实例

return constructs[0].newInstance(obj);

}catch(Exception e){

throw e;

}

}

}

/**

*构造函数参数注入

**/

public interface ConstructorParamDeal {

/**

*根据构造函数中参数的类型注入,相应的实现

@param classes 构造函数的参数类型

return 注入构造函数的参数实现

**/

public Object [] dealParam(Class [] classes);

}

public class ParamDeal implements ConstructorParamDeal{

/* (non-Javadoc)

* @see com.topsec.tsm.agent.helper.ConstructorParamDeal#dealParam(java.lang.Class[])

*/

public Object [] dealParam(Class[] classes) {

Object [] obj=new Object[classes.length];

for (int i=0;i<obj.length;i++){

//为不同类型注入选择不同实例

if (classes[i].equals(String.class)){

obj[i]=”Hello World”;

}

}

return obj;

}

}

上面的程序中ConstructorHelper用于利用反射机制枚举出载入类的构造函数及构造函数的参数的类型,至于不同类型注入什么样的实例则由ContructorParamDeal的实现者来决定,ContructorParamDeal的实现者同样可以以组件的形式在运行时动态载入。由于组件间的依赖关系的制约,所以组件实例化的顺序需要特别考虑。

4 结束语

三种依赖注入模式各有其特点和优势,只有充分理解这些模式间的不同,才能为自己的应用选择正确的依赖注入模式,文中介绍的构造子注入模式实现方法,在使用其他具有反射功能的语言(如:.NET)时同样可以参考。

[参考文献]

1 Martin Fowler,Inversion of Control Containers and the Dependency Injection pattern,http://www.martinfowler.com/articles/injection.html,2004

2 Erich Gamma,Design Patterns,Addison Wesley,1999

3 http://www.picocontainer.org/

4 彭晨阳,Ioc模式, http://www.jdon.com,2004

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结