资料1:
IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection)。
作用:将各层的对象以松耦合的方式组织在一起,解耦,各层对象的调用完全面向接口。当系统重构的时候,代码的改写量将大大减少。
理解依赖注入:
当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常有调用者来创建被调用者的实例。然而采用依赖注入的方式,创建被调用者的工作不再
由调用者来完成,因此叫控制反转,创建被调用者的实例的工作由IOC容器来完成,然后注入调用者,因此也称为依赖注入。
举个有意思的例子(来源于互联网)
假如我们要设计一个Girl和一个Boy类,其中Girl有Kiss方法,即Girl想要Kiss一个Boy,首先问题是Girl如何认识Boy?
在我们中国常见的MM认识GG的方式有以下几种:
A 青梅竹马 B 亲友介绍 C 父母包办
哪一种是最好的?
1.青梅竹马:很久很久以前,有个有钱的地主家的一闺女叫Lily,她老爸把她许配给县太爷的儿子Jimmy,属于指腹为婚,Lily非常喜欢kiss,但是只能kiss Jimmy
- public class Lily{
- public Jimmy jimmy;
- public Girl()
- {
- jimmy=new Jimmy();
- }
- public void Kiss()
- {
- jimmy.Kiss();
- }
- }
- public class Jimmy
- {
- public void Kiss()
- {
- Console.WriteLine("kissing");
- }
- }
这样导致Lily对Jimmy的依赖性非常强,紧耦合。
2.亲友介绍:经常Kiss同一个人令Lily有些厌恶了,她想尝试新人,于是与Jimmy分手了,通过亲朋好友(中间人)来介绍
- public class Lily{
- public Boy boy;
- public Girl()
- {
- boy=BoyFactory.createBoy();
- }
- public void Kiss()
- {
- boy.Kiss();
- }
- }
亲友介绍,固然是好。如果不满意,尽管另外换一个好了。但是,亲友BoyFactory经常是以Singleton的形式出现,不然 就是,存在于Globals,无处不在,无处不能。实在是太繁琐了一点,不够灵活。我为什么一定要这个亲友掺和进来呢?为什么一定要付给她介绍费呢?万一
最好的朋友爱上了我的男朋友呢?
3.父母包办:一切交给父母,自己不用非吹灰之力,Lily在家只Kiss
- public class Lily{
- public Boy boy;
- public Girl(Boy boy)
- {
- this.boy=boy;
- }
- public void Kiss()
- {
- this.boy.Kiss();
- }
- }
Well,这是对Girl最好的方法,只要想办法贿赂了Girl的父母,并把Boy交给他。那么我们就可以轻松的和Girl来Kiss了。看来几千 年传统的父母之命还真是有用哦。至少Boy和Girl不用自己瞎忙乎了。这就是IOC,将对象的创建和获取提取到外部。由外部容器提供需要的组件。
在设计模式中我们应该还知道依赖倒转原则,应是面向接口编程而不是面向功能实现,好处是:多实现可以任意切换,我们的Boy应该是实现Kissable接口。这样一旦Girl不想kiss可恶的Boy的话,还可以kiss可爱的kitten和慈祥的grandmother
好在.net中微软有一个轻量级的IoC框架Unity,支持构造器注入,属性注入,方法注入如下图所示
具体使用方法如下图所示
- using System;
- using Microsoft.Practices.Unity;
- namespace ConsoleApplication9
- {
- class Program
- {
- static void Main(string[] args)
- {
- //创建容器
- IUnityContainer container=new UnityContainer();
- //注册映射
- container.RegisterType<IKiss, Boy>();
- //得到Boy的实例
- var boy = container.Resolve<IKiss>();
- Lily lily = new Lily(boy);
- lily.kiss();
- }
- }
- public interface IKiss
- {
- void kiss();
- }
- public class Lily:IKiss
- {
- public IKiss boy;
- public Lily(IKiss boy)
- {
- this.boy=boy;
- }
- public void kiss()
- {
- boy.kiss();
- Console.WriteLine("lily kissing");
- }
- }
- public class Boy : IKiss
- {
- public void kiss()
- {
- Console.WriteLine("boy kissing");
- }
- }
- }
如果采用配置文件注册的话
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <configSections>
- <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/>
- </configSections>
- <unity>
- <containers>
- <container name="defaultContainer">
- <register type="命名空间.接口类型1,命名空间" mapTo="命名空间.实现类型1,命名空间" />
- <register type="命名空间.接口类型2,命名空间" mapTo="命名空间.实现类型2,命名空间" />
- </container>
- </containers>
- </unity>
- </configuration>
配置的后台代码:
- UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName)
- as UnityConfigurationSection;
- configuration.Configure(container, "defaultContainer");
可以通过方法ResolveAll来得到所有注册对象的实例:
var Instances = container.Resolve<IKiss>();
Martin Fowler在那篇著名的文章《Inversion of Control Containers
and the Dependency Injection pattern》中将具体依赖注入划分为三种形式,即构造器注入、属性(设置)注入和接口注入,习惯将其划分为一种(类型)匹配和三种注入:
- 类型匹配(Type Matching):虽然我们通过接口(或者抽象类)来进行服务调用,但是服务本身还是实现在某个具体的服务类型中,这就需要某个类型注册机制来解决服务接口和服务类型之间的匹配关系;
- 构造器注入(Constructor
Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象; - 属性注入(Property
Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性; - 方法注入(Method
Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。
我们创建一个控制台程序,定义如下几个接口(IA、IB、IC和ID)和它们各自的实现类(A、B、C、D)。在类型A中定义了3个属性B、C和 D,其类型分别为接口IB、IC和ID。其中属性B在构在函数中被初始化,以为着它会以构造器注入的方式被初始化;属性C上应用了 DependencyAttribute特性,意味着这是一个需要以属性注入方式被初始化的依赖属性;属性D则通过方法Initialize初始化,该方 法上应用了特性InjectionMethodAttribute,意味着这是一个注入方法在A对象被IoC容器创建的时候会被自动调用。
- public interface IA { }
- public interface IB { }
- public interface IC { }
- public interface ID { }
- public class A : IA
- {
- public IB B { get; set; }
- [Dependency]
- public IC C { get; set; }
- public ID D { get; set; }
- public A(IB b)
- {
- this.B = b;
- }
- [InjectionMethod]
- public void Initalize(ID d)
- {
- this.D = d;
- }
- }
- public class B : IB { }
- public class C : IC { }
- public class D : ID { }
然后我们为该应用添加一个配置文件,并定义如下一段关于Unity的配置。这段配置定义了一个名称为defaultContainer的Unity容器,并在其中完成了上面定义的接口和对应实现类之间映射的类型匹配。
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <configSections>
- <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/>
- </configSections>
- <unity>
- <containers>
- <container name="defaultContainer">
- <register type="UnityDemo.IA,UnityDemo" mapTo="UnityDemo.A, UnityDemo"/>
- <register type="UnityDemo.IB,UnityDemo" mapTo="UnityDemo.B, UnityDemo"/>
- <register type="UnityDemo.IC,UnityDemo" mapTo="UnityDemo.C, UnityDemo"/>
- <register type="UnityDemo.ID,UnityDemo" mapTo="UnityDemo.D, UnityDemo"/>
- </container>
- </containers>
- </unity>
- </configuration>
最后在Main方法中创建一个代表IoC容器的UnityContainer对象,并加载配置信息对其进行初始化。然后调用它的泛型的Resolve方法创建一个实现了泛型接口IA的对象。最后将返回对象转变成类型A,并检验其B、C和D属性是否是空
- class Program
- {
- static void Main(string[] args)
- {
- UnityContainer container = new UnityContainer();
- UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection;
- configuration.Configure(container, "defaultContainer");
- A a = container.Resolve<IA>() as A;
- if (null!=a)
- {
- Console.WriteLine("a.B==null?{0}",a.B==null?"Yes":"No");
- Console.WriteLine("a.C==null?{0}", a.C == null ? "Yes" : "No");
- Console.WriteLine("a.D==null?{0}", a.D == null ? "Yes" : "No");
- }
- }
- }
从如下给出的执行结果我们可以得到这样的结论:通过Resolve<IA>方法返回的是一个类型为A的对象,该对象的三 个属性被进行了有效的初始化。这个简单的程序分别体现了接口注入(通过相应的接口根据配置解析出相应的实现类型)、构造器注入(属性B)、属性注入(属性 C)和方法注入(属性D)
a.B == null ? No
a.C == null ? No
a.D == null ? No
资料2:
Unity系列:
Unity的Release又拖期了,不过对于patterns & practices的fans来说,应该习以为常了。
不少大牛已经开始深入岩洞探险了,向他们致敬:)鉴于之前的CTP或者weekly
drops实在是不够稳定,这也就成了我站在洞口静观其变的理由...ok,说白了就是懒惰的借口。
不过这并不妨碍我们先聊聊ObjectBuilder。我们需要一些预备知识,如果对于IOC还不是很清楚的话,还是应该在IOC的理解花点时间。Martin Fowler的Inversion
of Control Containers and the Dependency Injection pattern 是不得不推荐的,当然,如果读起来比较费劲的话,吕震宇的你真的了解Ioc与AOP吗?系列也是不错的参考。
ObjectBuilder 是一个可扩展的Application
Block,同时也是Unity的基础。对于ObjectBuilder到底是什么有很多不同的理解,最普遍的描述是:ObjectBuilder是“依 赖注入的工具”,还有一种说法是“构建依赖注入容器的框架”,这些多少都有些出入。应该说ObjectBuilder是一个可配置的对象工厂。
ObjectBuilder有两个版本,习惯称之为OB1和OB2。OB1在ObjectBuilder的网站上就可以down,OB2可以说目前只流传于民间:)为什么这么说?因为目前只能从Unity的CTP版本中或者sample中得到,估计是会跟随Unity的Release一块儿发布。
本想结合一个例子来谈谈自己对ObjectBuilder的理解,不过已有的一些资料写得很好,班门弄斧实在没有必要,而且说实话自己的理解也真的很有限。
对于OB1,黄忠成先生的Object
Builder Application Block系列写得清晰透彻,当然,经过吕震宇先生的简体翻译:[转]Object Builder Application Block,可读性更上一层楼了。
OB2在OB1的基础上可以说有了很大的改动,这里可以参考Chris的最新的一些blog,顺带说一句,这些blog读起来就要费些脑筋了,如果你不是真的想对ObjectBuilder弄个门清的话,那其实理解一下黄忠成先生文章中提到的一些概念和思想就足够了。
在之前的 Unity(一):从ObjectBuilder说起 一文中,介绍了Unity的底层框架ObjcetBuilder。同时我稍微也提了一句:他们(patterns &
practices)再次犯了拖期的老毛病(说句公道话,这个毛病当属其中的Enterprise Library团队最为突出)。没想到,今天看到Unity的掌门Grigori说:Unity
release date is moved to April 7。又有得等了:)
实在不想针对目前的不稳定版本做介绍,主要是看到3月12号的版本比一个月前的CTP有了很大的改动,怕了!不想等正式版出来之后,目前的介绍中的代码编译一下n多错误——如果不彻头彻尾的改一遍的话。这里有篇文章介绍了这两个版本之间的改动:IoC
Container, Unity and Breaking Changes Galore。Huh!之前TerryLee也有一篇介绍的文章:依赖注入容器Unity Application Block(1):快速入门,是针对2月的CTP的,嗯,没错,他的示例代码中用到的API现在都变了...
不过,在这个时候做一些总体性的介绍还是合适的,毕竟大的方向已经不会变的。嗯,至少我是这么想的...
Unity是什么?
Unity是patterns & practices团队开发的一个轻量级、可扩展的依赖注入容器,具有如下的特性:
1. 它提供了创建(或者装配)对象实例的机制,而这些对象实例可能还包含了其它被依赖的对象实例。
2. Unity允许将预先配置的对象注入到类中,实现了inversion
of control (IoC)的功能。在Unity中,支持constructor
injection(构造器注入), property setter injection(属性设值注入)以及method call injection(方法注入)。ps:这里的方法注入与Martin Fowler定义的接口注入类似而稍有区别。
3. 支持容器的体系结构。一个容器可以有子容器,允许从子容器到父容器的对象定位查询。
4. 可以通过配置文件进行准备和配置容器。
5. 不会影响到类的定义(属性设值注入和方法注入除外),这也是轻量级容器的一个体现。
6. 支持自定义的容器扩展。
Microsoft patterns & practices 终于发布了Unity,可以从这里下载,同时可以参见Unity的老大Grigori Melnik的发布说明,不过比下载页面也没多什么信息。
最近patterns & practices团队采用了一种新的文档发布模式,就是Release版本的产品在发布的同时,将产品文档同步到msdn中。这样的话,如果你只想先了
解下产品,或者先了解再决定是否安装的话,可以先在msdn上浏览,而不必安装->看文档->卸载。
在这里,首先给出一个简单使用Unity的简单示例。通过示例,让我们对Unity的使用有一个初步的认识和了解。在接下来的文章中,我会展开做详细的介绍和分析。如果对Unity是还一点概念也没有的话,可以参考我前面的两篇文章:Unity(一):从ObjectBuilder说起,Unity(二):Unity是什么?。
本示例主要展示了Unity在type mapping方面的一个基本应用。
使用Unity,最基本步骤有三步。
1. 建立容器;
2. 将接口与类的映射注册到容器中;
3. 从容器中解析出正确的对象。
为了接下来的说明,我们先编写几个后面需要的接口和类:
Step0. 准备工作
ILogger接口:
ConsoleLogger类:
NullLogger类:
Step1. 创建容器
在Unity中创建容器实例最简单的方法是直接使用构造函数创建,如下代码所示:
IUnityContainer container = new UnityContainer();
Step2. 注册接口映射
在Unity中提供了一组Register方法供我们在容器中注册接口映射,如下代码所示:
container.RegisterType<ILogger, ConsoleLogger>();
Step3. 获取对象实例
在Unity中提供了一组Resolve方法用以获取对象实例,如下代码所示:
ILogger logger = container.Resolve<ILogger>();
OK,就这么简单!
测试:
场景一:注册一个ConsoleLogger作为ILogger的实现到容器中,然后从容器中解析ILogger的实现,并调用ILogger的Log方法。
结果:
场景二:注册一个NullLogger作为ILogger的实现到容器中,然后从容器中解析ILogger的实现,并调用ILogger的Log方法。
结果:
在之前的一篇文章“Unity(三):快速入门”中,给出了一个简单的示例,让我们对Unity有一个感性的认识。但是,Unity到底是做什么用的呢?一个简单示例并不能解答这个问题。
总的来看,Unity的使用场景主要有以下几个:
- 建立类型映射
- 用于单例模式
- 用于依赖注入
接下来,将逐一介绍各使用场景。
在创建对象时,毫无疑问,类型映射是我们无法回避的一个问题,同时也是一系列工厂模式的根本出发点。类型映射,为面向对象设计的根本原则——“针对接口编程,而不是针对实现编程”、“要依赖抽象,不要依赖具体类”——在应用中的实现,提供了有力的支持。
我们知道,Unity提供了对象的容器,那么这个容器是如何进行索引的呢?也就是说,容器内的单元是如何标识的呢?在Unity中,标识主要有两种 方式,一种是直接使用接口(或者基类)作为标识键,另一种是使用接口(或者基类)与名称的组合作为标识键。键对应的值就是具体类。
- 用接口类型作为标识键
实际上,之前的“Unity(三):快速入门”中给出的例子,就是接口类型作为标识键的一个使用场景,这里就不再重复。
这里需要指出的是,Unity提供的功能都有泛型和非泛型两个版本,这样可以确保 Unity 在不支持泛型的环境中使用。我们在代码中可以使用任意一种方法(泛型和非泛型),或者根据需要混合使用。例如,可以使用泛型形式来注册映射,然后使用非泛型形式去获取对象实例。
下面的代码演示了非泛型形式的使用:
ps:接下来的例子以及后续文章中的例子将不再演示非泛型形式的使用。
- 用基类作为标识键
用基类作为标识键,在本质上与用接口类型作为标识键是一样的。这里需要注意的是,基类并不一定是指抽象类。下面是使用基类作为标识键的例子:
有趣的是,这里如果直接container.RegisterType<Logger, Logger>();的话,是可以Resolve出来的,但是如果我们container.RegisterType<ILogger,
ILogger>();的话,运行时就会报异常了。
- 用接口(或基类)与名称的组合作为标识键
如果需要使用同样的接口(或基类)注册多个映射,可以指定名称来区分每个映射。在需要Resolve的时候,通过指定接口(或基类)与名称的组合作
为标识键,就可以获取恰当类型的对象。下面的例子以接口与名称的组合为例,基类与名称的组合作为标识键同理,不再赘述(有关Ilogger等代码见“Unity(三):快速入门”中给出的准备代码)。:
这里需要指出的是,注册名称是一个字符串,如果需要,可以包含空格。它们是大小写敏感的。例如,名称“Mymapping”和“MyMapping”将指的是二个不同的注册映射。
我们说到哪了...
对于Unity的底层构件ObjectBuilder
介绍了Unity的特性。
给出一个简单使用Unity的简单示例,对Unity的使用有一个初步的认识和了解。
介绍了如何使用Unity建立类型的映射。
单例模式
有关单例模式本身,我想就不用我在这里多说了,它可以说是模式中最简单的一个了。我只是想谈谈我对这个模式的一点想法。
为 了实现单例模式,我们通常的做法是,在类中定义一个方法如GetInstance,判断如果实例为null则新建一个实例,否则就返回已有实例。但是我觉 得这种做法将对象的生命周期管理与类本身耦合在了一起,与SRP原则相违背。所以我觉得遇到需要使用单例的地方,应该将生命周期管理的职责转移到对象容器 上,而我们的类依然是一个干净的类。
为了接下来的说明,我们先编写几个后面需要的接口和类:
准备工作
IOrder接口、CommonOrder类、VipOrder类:
将Unity应用于单例模式,主要有两种使用场景:
(1)将类型注册为单例;
(2)将已有对象注册为单例。
将类型注册为单例
与上一篇文章建立类型映射类似,我们可以将一个接口注册为标识键,对应一个具体类。不同的是,要用于单例模式,我们需要使用一个LifetimeManager类型的参数,并指定为ContainerControlledLifetimeManager类型:
RegisterType<TFrom, TTo>(new
ContainerControlledLifetimeManager())
以下的代码以接口注册为例,演示了如何为一个接口注册一个具体类,并声明为单例:
同样,我们可以为一个基类注册一个具体类,与前述的类似,如:
container.RegisterType<Order, CommonOrder>(new
ContainerControlledLifetimeManager());
我们还可以把某个类直接注册为单例,而不进行类型的映射,如:
container.RegisterType<CommonOrder>(new
ContainerControlledLifetimeManager());
这里需要指出的是:默 认情况下,直接使用RegisterType(不指定LifetimeManager),然后Resolve所得到的对象的生命周期是短暂的,容器并不保
存对象的引用,每次调用Resolve方法我们都会得到一个新对象。然而,当我们指定了 ContainerControlledLifetimeManager参数后,容器在第一次调用Resolve方法时会创建一个新对象,此后在每次调用 Resolve时,都会返回之前创建的对象。
除此之外,Unity还扩展了单例的使用方式。例如,我需要把某个类设计为单例,但是发现在应用中我们需要这个类的两个单例,分别有不同的用途,这时,使用Unity的类型与名称组合的标识键,就可以满足这种使用场景。使用方式如下(以接口为例):
ps:使用类型与名称的组合的例子可以参见前面一篇文章,大致相同。
同理,我们也可以用基类和名称的组合为标识键注册具体类,还可以直接把某个类和名称组合进行注册。这里不再赘述。
将已有对象注册为单例
如果我们已经创建了一个对象,并希望使用已创建的对象作为单例,而不是由容器新创建一个对象,那么我们应该使用RegisterInstance方法。以下的代码以接口注册为例,演示了如何使用一个已有对象,为一个接口注册一个具体类,并声明为单例:
将已有对象注册为单例,同样也支持前面提到的几种使用方式,如注册基类、使用名称等,也就不再重复了。
我们说到哪了...
- Unity(一):从ObjectBuilder说起
- Unity(二):Unity是什么?
- Unity(三):快速入门
- Unity(四):使用场景Ⅰ:建立类型映射
- Unity(五):使用场景Ⅱ:用于单例模式
有关依赖注入
什么是依赖注入,我想这个问题我在这里说就不大合适了,所以还是推荐一下大师的文章。之前的文章也提到过,“Martin Fowler的Inversion of Control
Containers and the Dependency Injection pattern 是不得不推荐的,当然,如果读起来比较费劲的话,吕震宇的你真的了解Ioc与AOP吗?系列也是不错的参考”。
这里要多说一句的是,依赖注入(Dependency Injection)和控制反转(Inversion of Control)并不是同一个概念,有的人经常混在一起。有关这一点,看看前面说的Martin的文章就明白了。
准备工作
为了接下来的说明,我们先编写几个后面需要的接口和类。
注:本文涉及到的IOrder接口、CommonOrder类、VipOrder类和ILogger接口、ConsoleLogger类、NullLogger类等代码请参见Unity(三):快速入门和Unity(五):使用场景Ⅱ:用于单例模式中的准备代码,这里不再重复。
IOrderWithLogging接口:
Unity支持的依赖注入方式有三种:构造器注入、设值注入、方法调用注入。
构造器注入
对于构造器注入,Unity支持两种方式的依赖注入:自动依赖注入和通过打标签标识。具体来说,对于只有单一构造函数的目标类,可以使用自动依赖注入的方式,而对于有多个构造函数的目标类,则应该使用在构造函数上打标签的方式来进行注入。
- 单构造器自动注入
下面是我们的目标类SingleConstructor,可以看到,SingleConstructor类只有一个构造函数,构造函数中有两个参数:IOrder和ILogger。
下面是我们的测试代码。首先,我们建立容器,然后在容器中注册了IOrder和ILogger的具体类,同时将SingleConstructor注册为IOrderWithLogging的具体类。
在这里,我们将CommonOrder注册为单例,并为它的单实例的Discount赋值为0.8,这样做的目的是为了检验取出的IOrder的具体类为我们先前的注入到容器的CommonOrder类。
下面是输出结果。
- 多构造器打标签注入
下面是我们的目标类MultipleConstructor,可以看到,MultipleConstructor类有两个构造函数,一个不带参数,一个有两个参数:IOrder和ILogger,后者打了InjectionConstructor标签。
下面是我们的测试代码。思路与上面的例子类似,在这里我们把IOrder和ILogger对应的具体类换成了VipOrder和NullLogger,IOrderWithLogging对应的具体类换成了MultipleConstructor。
下面是输出结果。
这里需要注意的是,如果在上面的 MultipleConstructor类中,我们不打InjectionConstructor标签,运行结果是一样的,大家可以自己试验一下。
这是因为对于具有多个构造器的目标类,如果没有发现标有InjectionConstructor标签的构造器,Unity会自动使用参数最多的构造器。如果发现参数最多的构造器同时有多个,那么就会在运行时抛出一个异常。比如说,如果我们的MultipleConstructor类中构造函数不打InjectionConstructor标签,同时还有一个构造函数也有两个参数,结果就是这样。
s