一. 温故而知新
在开始学习Unity框架之前,有必要温习一下 【依赖倒置原则】和【手写IOC】, 因为我们框架代码的构建都是基于【依赖倒置原则】的,而Unity框架的核心思想就是IOC和DI,所以有必要了解通过【反射+工厂+配置文件】来实现手写IOC的原理。
废不多说,简单提炼一下依赖原则的核心理念:面向接口编程。上层(UI层)不直接依赖于下层,而是依赖于下层对应的接口,下层去实现该接口。
经典案例:业务层连接数据库,依赖接口,可以实现不改代码轻松切换数据库的目的。
手动创建对象的三个阶段:直接new实例化→面向接口编程→将创建对象的交给工厂【手写IOC】。
1 #region 手动创建对象几个阶段 2 { 3 Console.WriteLine("--------------一.复习依赖倒置原则和手写IOC(简单工厂+反射+配置文件)----------------"); 4 //1. 直接创建(需要添加对Service层的引用) 5 Console.WriteLine("--------------1. 直接创建----------------"); 6 AndroidPhone androidPhone1 = new AndroidPhone(); 7 androidPhone1.Call(); 8 9 //2. 利用接口进行改造(需要添加对Service和Interface层的引用) 10 Console.WriteLine("--------------2. 利用接口进行改造----------------"); 11 IPhone androidPhone2 = new AndroidPhone(); 12 androidPhone2.Call(); 13 14 //3. 手写IOC(只需要添加Interface层的引用,但需要把Service层生成的DLL拷贝到该项目bin下) 15 Console.WriteLine("--------------3. 利用接口进行改造----------------"); 16 IPhone androidPhone3 = ObjectFactory.CreateObject<IPhone>(); 17 androidPhone3.Call(); 18 19 //总结:第三个阶段基本上已经是我们自己写容器的最高层次 20 //下面我们将借助微软的第三方容器Unity来创建 21 22 } 23 #endregion
依赖倒置原则详见:http://www.cnblogs.com/yaopengfei/p/7101347.html
手写IOC详见:http://www.cnblogs.com/yaopengfei/p/6891286.html
二. Unity深入浅出
(一). Unity介绍
1. Unity的获取方式
①:下载网址:http://unity.codeplex.com/
②:通过Nuget获取 (目前最新版本为4.0.1)
2. Unity是什么?
Unity是一个IOC容器,用它可以来创建对象,代替手写反射,它可以实现依赖注入(DI),它出自于微软。.Net平台下类似的其它IOC框架还有:Spring.Net、AutoFac、Castle等等。
3. IOC的含义
IOC是一种目标,IOC的含义为控制反转,即原本上端(UI层)直接创建或使用的对象,交给第三方容器来装配和创建。
4. DI的含义
DI是一种手段,DI的含义为依赖注入,Unity提供三种方式分别是:属性注入、方法注入、构造函数注入。
(二). 使用Unity来创建对象
1. 创建对象的步骤
①:声明IUnityContainer容器。
②:通过RegisterType注册新类型 或者 通过RegisterInstance注册已经存在的类型。
③:通过Resove解析对象。
特别注意:注册类型时,可以指定名称来注册,用于同时创建注册多个类型时,Resove可以通过指定的注册名称来针对指定解析。
2. 代码说明
①:代码结构(后面所有的代码说明都是基于这个结构的)
详细说明:各个类的内容待写。
②:直接创建简单对象(需要添加对Interface层和Service层的引用)
1 { 2 Console.WriteLine("------------------- 二. Unity入门 -------------------"); 3 Console.WriteLine("------------------- 01-直接创建对象 -------------------"); 4 IUnityContainer container = new UnityContainer(); 5 //进行注册,可以是: 接口-类、父类-子类、抽象类-子类。 6 //container.RegisterType<IPhone, AndroidPhone>(); 7 //也可以用实例注册已经存在的对象 8 AndroidPhone android = new AndroidPhone(); 9 container.RegisterInstance<IPhone>(android); 10 //解析对象 11 IPhone phone = container.Resolve<IPhone>(); 12 phone.Call(); 13 //我们会发现,虽然注册了AndroidPhone对象,但是该对象中的三个属性并没有实例化,依旧为空 14 Console.WriteLine("phone.iHeadphone==null? {0}", phone.iHeadphone == null); 15 Console.WriteLine("phone.iMicrophone==null? {0}", phone.iMicrophone == null); 16 Console.WriteLine("phone.iPower==null? {0}", phone.iPower == null); 17 }
详解:上述代码我们分别通过RegisterType注册类型、RegisterInstance注册已经存在的类型,我们会发现最终生成AndoridPhone中的三个属性并没有被实例化,那么怎么实例化里面的属性呢?这就涉及到后面的依赖注入了。
③. 指定命名来创建对象(需要添加对Interface层和Service层的引用)
1 { 2 Console.WriteLine("------------------- 03-同时创建多个对象 -------------------"); 3 //可以通过添加一个参数来区分 4 IUnityContainer container = new UnityContainer(); 5 //注册类型时(指定命名) 6 container.RegisterType<IPhone, AndroidPhone>(); 7 container.RegisterType<IPhone, ApplePhone>("apple"); 8 container.RegisterType<IPhone, AndroidPhone>("android"); 9 //依赖注入 10 container.RegisterType<IMicrophone, Microphone>(); 11 container.RegisterType<IHeadphone, Headphone>(); 12 container.RegisterType<IPower, Power>(); 13 //解析对象 14 IPhone iphone1 = container.Resolve<IPhone>(); 15 iphone1.Call(); 16 //解析对象(指定命名) 17 IPhone iphone2 = container.Resolve<IPhone>("apple"); 18 iphone2.Call(); 19 IPhone iphone3 = container.Resolve<IPhone>("android"); 20 iphone3.Call(); 21 22 //获取所有已注册且指定命名的对象(未指定命名的不能获取) 23 var list = container.ResolveAll<IPhone>(); 24 25 }
详解:注册类型时,如果默认不指定命名,则后面的会覆盖前面的,解析多个对象时,永远是最后一个注册的类型的对象。 所以RegisterType指定命名 、Resolve解析对象指定命名广泛用于同时创建多个对象的情况。
我们发现上述代码有三句是依赖注入的,这个在下面讲解。
(三). 依赖注入的三种形式
Unity实现依赖注入有三种形式,分别是构造函数注入、属性注入、方法注入,分别对应三个特性:【InjectionConstructor】、【Dependency】、【InjectionMethod】,加上这三个特性表明构造函数中、属性中、方法中需要注入对象,使其实例化。
方式一:属性注入。
有WinPhone1类,实现了Iphone接口。其中WinPhone1类中的iMicrophone和iHeadphone两个属性上分别添加【Dependency】和【Dependency("fk")】,表明该属性对应的对象需要注入,其中iHeadphone需要Unity容器"指定命名"来注册类型。
1 public interface IPhone 2 { 3 void Call(); 4 IMicrophone iMicrophone { get; set; } 5 6 IHeadphone iHeadphone { get; set; } 7 8 IPower iPower { get; set; } 9 }
1 /// <summary> 2 /// 测试属性注入的类 3 /// </summary> 4 public class WinPhone1 : IPhone 5 { 6 [Dependency("fk")] //表示该属性需要注入,且需要指定名称 7 public IMicrophone iMicrophone { get; set; } 8 [Dependency] //表示该属性需要注入 9 public IHeadphone iHeadphone { get; set; } 10 public IPower iPower { get; set; } 11 12 public WinPhone1() 13 { 14 Console.WriteLine("{0}被构造", this.GetType().Name); 15 } 16 17 public void Call() 18 { 19 Console.WriteLine("{0}打电话", this.GetType().Name); ; 20 } 21 }
1 IUnityContainer container = new UnityContainer(); 2 //方式一: 属性注入 3 Console.WriteLine("----------------------------方式一: 属性注入 -------------------------------"); 4 container.RegisterType<IPhone, WinPhone1>(); 5 //指定命名注入 6 container.RegisterType<IMicrophone, Microphone>("fk"); 7 //普通注入 8 container.RegisterType<IHeadphone, Headphone>(); 9 IPhone phone1 = container.Resolve<IPhone>(); 10 phone1.Call();
补充说明:我们发现Microphone和HeadPhone均被构造,说明这两个类被注入成功。
方式二:方法注入。
有WinPhone2类,实现了Iphone接口。其中在WinPhone2中的方法Init1234方法上加【InjectionMethod】特性,表示该方法中有对象需要注入。
1 public class WinPhone2:IPhone 2 { 3 public IMicrophone iMicrophone { get; set; } 4 public IHeadphone iHeadphone { get; set; } 5 public IPower iPower { get; set; } 6 7 public WinPhone2() 8 { 9 Console.WriteLine("{0}被构造", this.GetType().Name); 10 } 11 12 public void Call() 13 { 14 Console.WriteLine("{0}打电话", this.GetType().Name); ; 15 } 16 17 [InjectionMethod] //方法注入,表明该方法中有对象需要注入 18 public void Init1234(IPower power) 19 { 20 this.iPower = power; 21 } 22 }
1 //方式二:方法注入 2 Console.WriteLine("----------------------------方式二:方法注入 -------------------------------"); 3 container.RegisterType<IPhone, WinPhone2>(); 4 container.RegisterType<IPower, Power>(); 5 IPhone phone2 = container.Resolve<IPhone>(); 6 phone2.Call();
补充说明:我们发现Power被构造,说明该类被注入成功。
方式三:构造函数注入(又分四种情况)
A. 默认注入。 自动识别参数数量最多的构造函数,表示该构造函数中有对象需要注入。
B. 指定构造函数。通过在构造函数上加 [InjectionConstructor]特性,表示该构造函数中有对象需要注入。
C. 指定构造函数且指定注入参数的名称。通过在构造函数上加 [InjectionConstructor]特性,并且在参数上加 [Dependency("fk")] ,表示该构造函数中的该参数中的对象需要注入。
D. 指定构造函数且指定注入参数的值(了解即可)。
1 /// <summary> 2 /// 测试构造函数注入(默认方式) 3 /// </summary> 4 public class WinPhone3 : IPhone 5 { 6 public IMicrophone iMicrophone { get; set; } 7 public IHeadphone iHeadphone { get; set; } 8 public IPower iPower { get; set; } 9 10 public WinPhone3(IPower iPower) 11 { 12 Console.WriteLine("{0}被构造", this.GetType().Name); 13 } 14 15 public void Call() 16 { 17 Console.WriteLine("{0}打电话", this.GetType().Name); ; 18 } 19 20 }
1 /// <summary> 2 /// 测试构造函数注入(指定构造函数) 3 /// </summary> 4 public class WinPhone4 : IPhone 5 { 6 public IMicrophone iMicrophone { get; set; } 7 public IHeadphone iHeadphone { get; set; } 8 public IPower iPower { get; set; } 9 public WinPhone4() 10 { 11 12 } 13 [InjectionConstructor] //指定该构造函数需要被注入 14 public WinPhone4(IPower iPower) 15 { 16 Console.WriteLine("{0}被构造", this.GetType().Name); 17 } 18 19 public void Call() 20 { 21 Console.WriteLine("{0}打电话", this.GetType().Name); ; 22 } 23 24 }
1 /// <summary> 2 /// 测试构造函数注入(指定参数依赖的注册名称) 3 /// </summary> 4 public class WinPhone5 : IPhone 5 { 6 public IMicrophone iMicrophone { get; set; } 7 public IHeadphone iHeadphone { get; set; } 8 public IPower iPower { get; set; } 9 public WinPhone5() 10 { 11 12 } 13 [InjectionConstructor] //指定该构造函数需要被注入且指定注入参数的名称 14 public WinPhone5([Dependency("fk")]IPower iPower) 15 { 16 Console.WriteLine("{0}被构造", this.GetType().Name); 17 } 18 19 public void Call() 20 { 21 Console.WriteLine("{0}打电话", this.GetType().Name); ; 22 } 23 24 }
/// <summary> /// 测试构造函数注入(指定参数值) /// </summary> public class WinPhone6 : IPhone { public IMicrophone iMicrophone { get; set; } public IHeadphone iHeadphone { get; set; } public IPower iPower { get; set; } public WinPhone6() { } [InjectionConstructor] //指定该构造函数需要被注入且指定注入参数的值 public WinPhone6(IPower iPower,string name) { Console.WriteLine("{0}被构造", this.GetType().Name); } public void Call() { Console.WriteLine("{0}打电话", this.GetType().Name); ; } }
1 //方式三:构造函数注入(又分4中情况) 2 /* 3 A. 默认方式 4 B. 指定构造函数 5 C. 指定参数依赖的注册名称 6 D. 指定参数值 7 */ 8 Console.WriteLine("-----------------------方式三:构造函数注入(又分4中情况)----------------------------"); 9 //A. 默认注入参数最多的构造函数 10 { 11 Console.WriteLine("-----------------------A. 默认注入参数最多的构造函数----------------------------"); 12 container.RegisterType<IPhone, WinPhone3>(); 13 container.RegisterType<IPower, Power>(); 14 IPhone iphone = container.Resolve<IPhone>(); 15 iphone.Call(); 16 } 17 //B. 指定构造函数 18 { 19 Console.WriteLine("-----------------------B. 指定构造函数----------------------------"); 20 container.RegisterType<IPhone, WinPhone4>(); 21 container.RegisterType<IPower, Power>(); 22 IPhone iphone = container.Resolve<IPhone>(); 23 iphone.Call(); 24 } 25 //C. 指定参数依赖的注册名称 26 { 27 Console.WriteLine("-----------------------C. 指定参数依赖的注册名称----------------------------"); 28 container.RegisterType<IPhone, WinPhone5>(); 29 container.RegisterType<IPower, Power>("fk"); 30 IPhone iphone = container.Resolve<IPhone>(); 31 iphone.Call(); 32 } 33 //D.指定参数值(了解即可) 34 { 35 Console.WriteLine("-----------------------D.指定参数值(了解即可)----------------------------"); 36 container.RegisterType<IPhone, WinPhone6>(new InjectionConstructor(new Power() { }, "ypf")); 37 IPhone iphone = container.Resolve<IPhone>(); 38 iphone.Call(); 39 }
小结一下: 以上内容分别介绍了IOC和DI的概念、Unity的使用方式和创建对象的方式、DI的三种注入方式。细心的人或者有经验的人一定会发现,上述代码依旧需要添加对Service层和Interface层的引用,那么用Unity有什么用?使用了Untiy来创建对象有何好处呢?
我们自己手写IOC的方式(反射+工厂+配置文件)的方式已经可以脱离对Service层的直接引用了,怎么引入Unity反而又需要添加对Service层的引用呢?这不是徒增烦恼么?
下面我们来解惑:上述所有代码(包括Unity步骤的三步走)都是在代码层次上,介绍Unity的基本API的使用,方便我们大家明白Unity有哪些功能,但在实际开发中,Unity三步走中的第二步,注册类型是通过配置文件来注册的,创建Unity容器和解析类型是通过代码来实现的→→这样就可以达到 脱离Service层的引用了(只需要把Service层的dll复制到主程序的bin文件下即可)。
有的人还会发现:即使使用了配置文件最多也就是和手写iOC打了个平手,并没有看到Unity特有的好处,我们下个章节将介绍的Unity的声明周期,即是Unity特有的一些封装,就可以见识到Unity的强大。