如何使用IOC和DI实现松耦合应用程序
这是本人第一篇博客园的文章,初衷是总结自己学到的知识内容,另外也希望和大家分享一些开发方面的经验。本人是一名在校的大学生,开发经验有限,肯定存在很多逻辑的误区和知识的盲点,如果有不妥的地方还请大家友好地进行指点,在此先谢谢大家了。
这篇博文是关于IOC(Inverse of control)和DI(Dependency Injection)的,主题是讨论IOC和DI怎么帮助我们构建松耦合的软件架构的。
问题:紧耦合
在我们了解IOC和DI的具体的含义前,让我们先理解下紧耦合这个问题。首先,考虑一下这个情形:有一个ClsCustomer类,它包含一个address(类型ClsAddress)对象,这样的设计将导致了类之间的耦合。可以说ClsCustomer这个类依赖address这个对象,如果ClsAddress这个类发生了变化,那么将会导致ClsCustomer进行重新编译。如下图所示:
图1:紧耦合
总结一下上述设计为什么会产生紧耦合:
(1)首先,也是最大的问题,ClsCustomer类控制了address这个对象的创建过程;
(2)ClsAddress这个类的对象直接被ClsCustomer这个类的对象引用,这将导致ClsAddress类和ClsCustomer类之间的耦合。
(3)ClsCustomer这个类指导ClsAddress这个类型的创建,所以如果我们添加一个新的地址类型,例如:ClsHomeAddress,ClsOfficeAddress,这将导致ClsCustomer这个类的更改。
所以,由此可知,当ClsAddress这个对象的创建过程出现了问题,那么ClsCustomer这个类的构造器在初始化时就会失败,这种相互的牵制就是耦合的坏处,进而导致程序的可扩展性降低。
解决方法:
在我们了解了紧耦合的缺点后,我们来了解下解决方法,其实理论很简单,即将ClsAddress对象的创建控制权从ClsCustomer类中进行分离,交由第三方进行控制,也就是说如果我们能将控制权反转给第三方,那我们就能解决上述问题了。下边将介绍的解决方法就是IOC(Inversion of control),即控制反转。
IOC的原理:
IOC的基本原理简述之就是Hollywood principle,即Do not call us we will call you。用更形象的话说就是类ClsAddress将对类ClsCustomer说:“不要创建我,我将在别处创建自己。”
在这里强调一下接口的重要作用,接口定义了一个类的行为准则,相当于类的功能清单,它提供了隐藏类细节的优点,接口对于降低耦合性以及提高程序的扩展性有着非常重要的作用。对于本例而言,如果主类聚合了其它类,那它不应该直接聚合已实现类本身,而应该聚合的是类的接口,即ClsCustomer类聚合的应该是ClsAddress这个类的接口IAddress。
图2.IOC Framework架构
上图显示了解藕的方法,可以看出ClsAddress的创建过程委托给了IOC Framework,这么设计将ClsAddress的创建过程从ClsCustomer中分离了,从而实现了ClsAddress和ClsCustomer的解藕,解藕的过程可分为两步:
(1)IOC framework创建ClsAddress对象
(2)IOC framework将对象引用交给ClsCustomer类。
实现IOC的方法:
在我们理解问题和解决思路后,我将介绍几种IOC的解决方案,由于IOC是通过DI(Dependency injection),即依赖注入实现的。所以我将重点介绍依赖注入的几种方法。
图3.IOC和DI间的组织
上图显示了IOC和DI之间是怎么组织的,所以我们可以认为IOC是一个理论,而DI是实现IOC的方法。DI中我们有四种实现的方法:
构造器方法
通过getter和setter注入
接口实现
service locator
在下面的部分里我们将通过亲自实践重点介绍下这些方法。
(1)构造器方法
使用这种方法,我们将ClsAddress的引用通过ClsCustomer的构造器进行传递,当ClsAddress对象被创建后,它的引用将以参数的形式传入ClsCustomer的构造器中。这种方法很简单,但是不适合在默认构造器经常被调用的情形。下图简要的介绍了这种方法的实现过程,其中IAddress是ClsAddress的接口:
图4.构造器方法实现DI
(2)getter和setter
这种方法是最通用的,而且我们经常用,就是通过get/set方法将依赖对象的引用进行赋值。但是这种方法有个缺点,它破坏了面向对象编程的封装性原则,对象因为使用了get/set方法,暴露了对象内部的细节,所以封装的不是很好,下图是该方法的代码实现:
图5.get/set方法实现DI
(3)以接口为基础的DI
在这种方法中,IOC Framework中包含了一个接口,如下图所示,即其中的IAddressDI,接口定义了一个方法setAddress,而ClsCustomer实现了这个接口,所以这样外部进行依赖注入时只用调用接口IAddressDI的setAddress方法就可以了,这屏蔽了ClsCustomer内部的细节,使得封装性更好了。具体思想参照下图:
图6.接口为基础的DI
(4)service locator
通过service locator,IAddress的引用将由service locator获得,如下图所示。service locator类并没有创建ClsAddress实例,它仅仅是提供了注册和寻找services的方法,而services创建了ClsAddress实例。具体示例请参照下一章节关于widsor框架的部分。
图7:service locator的使用
为什么我们不使用DI Factory呢?
首先想象,我们是不是可以通过工厂实现以上的内容呢?但这存在了一些问题,由单独的类工厂类处理所有创建对象的过程,这显得过于复杂了,下边是对工厂类局限的一些解释:
(1)硬编码:工厂类的最大问题是,它不能在应用程序之间进行重用,其中的内容都是硬编码,可复用性不高,而且随着类的增多工厂将变得复杂。
(2)接口依赖性:工厂类的基础是建立在通用接口的基础上的,即其创建的对象都要实现各自的通用接口,接口实现了对象创建(create)过程和实现(implement)过程解藕。但由此也引入了一些问题,由于所有的类都要实现通用的接口,但这对于特殊的类来说又是一个限制。
(3)所有的内容都是编译时确定的:工厂中,对象间的依赖关系必须在编译时确定下来,这极大限制了程序的灵活性。
Windsor框架
在这篇文章最后我将介绍一个非常有用的框架:Windsor
Windsor是一个IOC的开源项目,它的使用非常简单,它可以看做是service locator的一个应用,下面的图8到图10显示了Windsor使用的方法,在图11中介绍了使用配置文件实现DI的方法。在如下图所示编写完IAddress接口、ClsAdrress类以及ClsCustomer类后,在主类中首先创建Windsor container对象,第二步和第三步分别是在容器中注册类型(接口)和对象,第四步请求container创建customer对象,在这一步中container创建了ClsAdrress类的对象并通过ClsCustomer的构造方法实现了ClsCustomer对象的注入操作,最后一步释放了Customer对象。怎么样?这个框架的使用很简单把。本篇文章介绍的Windsor内容比较有限,它是一个非常强大的框架,希望大家能进一步学习。
图8.IAddress和ClsAddress的实现
图9:ClsCustomer的实现
图10:使用Windsor框架进行DI操作
图11显示了windsor中常用的配置文件实现DI的方法,具体的实现在这里就省略了,希望大家自己多尝试。
图11:使用XML配置windsor的DI操作
本篇文章介绍了IOC的概念和实现了方法,并且推荐了Windsor这个有用的框架,请大家多多尝试,最后花了大家这么长时间阅读,而且语句很不通顺,真是抱歉了,总之谢谢大家支持,欢迎大家和我多多进行交流,祝大家有个好心情!
posted on 2010-04-25 00:06 building7 阅读(1269) 评论(3) 编辑 收藏 举报