也谈谈我对IoC的理解
今天看到一篇 帖子谈到了对IoC的理解,于是我自己也忍不住跳出来“理解”一番,其实我当年也被“依赖注入、控制反转”这八个字折腾的够呛,但是搞明白之后就会发现这几个字、包括IoC这个名字都纯粹是唬人的,于是今天准备用三张图来说明白这件事。
首先,IoC和DI说的是一件事,它们的中文名分别是IoC(控制反转)和DI(依赖注入),要说依赖注入,就要先说说依赖倒置原则。
以下是直接贴自百度知道的概念:所谓依赖倒置原则就是要依赖于抽象,不要依赖于具体。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。
这当中提到了分层的概念,在“害死人”的三层架构当中,我们让UI引用BLL,让BLL引用DAL,也就是如下图所示,领域层里new了一个数据层的对象。
我图画的不好,领域层既然要new一个数据层对象,那么它就需要了解数据层对象的public成员,这样上层就依赖了下层,相当于把下层“拉”进了上层当中,形成了一种强依赖,在此当中数据层对象的生命周期是由领域层控制的(可能写在一个using块当中)。
为了松散耦合,改为如下方式:
在领域层当中提供一个接口(IRepository),让数据层来实现这个接口中的方法,这样一来领域层就无需引用数据层的dll了,数据层只需要知道领域层的接口即可,如此这个依赖关系就倒了个个,之后领域层要持有一个接口成员,这个成员是由领域层的构造函数赋值的(这个过程称为“构造注入”,是依赖注入的一种形式,GoF设计模式当中大量运用了这种手段)。
如此一来我们就完成了一个依赖倒置的示例,那么这个数据层的接口对象总要有一个地方来new它,而且显然是在代码的“高层”new出来的,那么我们放在什么地方来new它呢?如下图:
首先撇开图中的IoC部分不谈,我们大可以在展现层new一个数据层对象,然后沿着每一层的构造函数传递下去,这称为“穷人的依赖注入”,而这显然要求展现层引用底层的dll,是不合理的。
这样一来,就需要展现层去请求一个工厂类,让这个工厂类来负责创建数据层对象,然后把这个对象交给展现层,再传递下去。这个工厂类就是我们所谓的IoC容器。
这个工厂类内部通常包含一个私有的Dictionary<name,object>,可以根据名字来获取对象,对象具体的创建过程可以是反射一个配置好的XML,也可以是通过其他手段获取类名来反射创建,同时这个工厂也可以负责对象是否单例以及是否延迟加载等细节,另外对象的生命周期也是由这个工厂负责的。不过这个工厂有一个弊端,就是需要在站点启动的时候把它启动起来。
事实上,不光是数据层,连同领域层和服务层的对象,也可以从展现层通过IoC容器“推”到下层,这就是一套松散耦合的系统。
这样的系统方便单元测试,由于耦合度低方便零件的插拔,麻烦在于单步调试,而且对开发人员的OO能力有要求。
在实际应用当中我们大可以写一个自己的容器,如果你是做平台的,就可以写一些配置界面,让用户配置你功能的二次开发类,可以把这些二次开发接口的对象反射进容器里进行保存,这样一来我们的平台根本无需知道二次开发人员的子类是怎么实现,只需要知道这个类的位置即可。
最后,对于“依赖注入、控制反转”的理解,说白了,依赖注入就是我不去找你了,有人告诉我你在哪。控制反转就是控制权给你了,我不new了你来new。