接触依赖注入的概念有一段时间了,期间断断续续看了一些别的文章,慢慢也体会到了其中的基本思想.
依赖注入的出现动机是很简单的。在软件工程里,一直提倡松藕合,但怎么做到呢?在你的代码里,一切有变化的地方都应该依赖于抽象.但光抽象不行啊,总要有人做事啊,你还是需要具体的对象来完成你的工作. 假设我们有一个config,上面记录了所有我们要创建对象的Map,然后有这样的一个东西,一但我们需要什么,就这个东西说我要xxx,然后这个东东自动根据config创建我们需要的XXX送到我们自已手里.这个东东早就出现了,正式名字就叫做容器(Contain).
Microsoft 发布Enterprise Library4.1有一段时间了,Unity是作为一个单独的Application Block发布的,下面我将边看Unity document,边敲些代码验证,记录些自已的心得体会.
我想所有的Contain都必须实现核心需求:根据抽象接口和具体类的Mapping,返回具体类的实例.
那么首先我定义一个interface 和 两个实现 (啥事也不做):
编写第一个测试: 实例化Contain,并建立类型Mapping,生成实例类,打印Type
输出我们会看到UnitTest.PromptDataProcessor. OK,接着试验,我们创建第二个DataProcessor,
输出我们看到false, 默认情况下每次创建一个新的实例,如果想要单一实例怎么办,在Cotain里有一个LifetimeManager概念,
看源代码列出主要有以下几种:
TransientLifetimeManager 即默认处理,容器完整创建对象后,不做任何处理
ContainerControlledLifetimeManager 容器持有,实现Singleton 模式
ExternallyControlledLifetimeManager 故名思意,自已外部管理对象生命周期,容器仅保存弱引用
PerThreadLifetimeManager 容器保证在每个线程中存在一个对象实例
SynchronizedLifetimeManager 抽象类,暂没研究
先来试试ContainerControlledLifetimeManager,代码如下:
可以看到返回的是同一实例. OK,再来看看ExternallyControlledLifetimeManager,直接替换ContainerControlledLifetimeManager,
结果是和ContainerControlledLifetimeManager一样的,仔细想想,当使用ExternallyControlledLifetimeManager时,
容器仅保存弱引用(可被垃圾回收),意味着可能是单一实例(没有回收)可能不是.
接下来再试试PerThreadLifetimeManager,测试这个要费点周折,代码如下:
可以看到,在同一个线程内创建的实例是同一实例,线程外侧不是.
现在回到核心需求: 假设我们定义的抽象接口要map到多个具体类,要怎么处理?,Unity Contain在注册时提供一个命名注册,
即你可以给类型Map时命名,看以下代码:
可以看到结果,输出分别是PrintDataProcessor 和 PromptDataProcessor类型.
这种Type + Name的设计来确定一种类型Map是非常灵活的,假设我们需要一个类型但两个实例处理不同方面的对象.
可以很容易实现,
Unity Contain 支持instance 注册,这时感觉Contain 扮演 Service Locator的角色,
现在测试一下UnityContainer的ResloveAll方法,依文档描述是返回 一个注册类型 的所有instance,测试代码如下:
很奇怪没有输出结果,留意文档里说的,If the container does not contain any named (non-default) mappings for the specified type, it will return null (in C#) or Nothing .想想背后的设计: 如果不是命名注册,ResolveAll 其实没有意义,因为非命名注册,只能对应一个具体类,Resolve就够了. 命名注册才有可能一个接口对应到多个具类,改一下码再测试:
下面来看看具体的依赖注入方式,依据文档描述,Unity具体支持三种依赖注入,Constructor injection,Property injection,Method Injection.首先来看Constructor injection.
Constructor Injection
构造函数注入方式有二种,其一是当创建对象只有一个构造函数时,自动依赖注入。其二是有多个构造函数时,使用InjectionConstructorAttribute,简单的先看:
看看有两个构造函数并用InjectionConstructor指定注入
现在假设构造函数依赖一个抽象一个特定的interface,
PropertyInjection
实现只有一种方式,通过给相应的属性标识为Dependency
Method Injection
实现也只有一种方式, 把相应的注入方法标识为InjectionMethod
Buildup方法
如果你已经有一个对象,但在每次使用前想使初始化一次,可以使用BuildUp方法达到这个目的.
Reference:
First Steps with Unity
http://www.nablasoft.com/alkampfer/index.php/2009/01/16/first-steps-with-unity/
IoC框架介绍
http://blog.csdn.net/wanghao72214/archive/2009/03/08/3969594.aspx
Martin Fowler的介绍文章
http://martinfowler.Mcom/articles/injection.html