01-Unity深入浅出(一)

时间:2023-03-10 00:20:06
01-Unity深入浅出(一)

一. 温故而知新

  在开始学习Unity框架之前,有必要温习一下 【依赖倒置原则】和【手写IOC】, 因为我们框架代码的构建都是基于【依赖倒置原则】的,而Unity框架的核心思想就是IOC和DI,所以有必要了解通过【反射+工厂+配置文件】来实现手写IOC的原理。

  废不多说,简单提炼一下依赖原则的核心理念:面向接口编程。上层(UI层)不直接依赖于下层,而是依赖于下层对应的接口,下层去实现该接口。

  经典案例:业务层连接数据库,依赖接口,可以实现不改代码轻松切换数据库的目的。

  手动创建对象的三个阶段:直接new实例化→面向接口编程→将创建对象的交给工厂【手写IOC】。

         #region 手动创建对象几个阶段
{
Console.WriteLine("--------------一.复习依赖倒置原则和手写IOC(简单工厂+反射+配置文件)----------------");
//1. 直接创建(需要添加对Service层的引用)
Console.WriteLine("--------------1. 直接创建----------------");
AndroidPhone androidPhone1 = new AndroidPhone();
androidPhone1.Call(); //2. 利用接口进行改造(需要添加对Service和Interface层的引用)
Console.WriteLine("--------------2. 利用接口进行改造----------------");
IPhone androidPhone2 = new AndroidPhone();
androidPhone2.Call(); //3. 手写IOC(只需要添加Interface层的引用,但需要把Service层生成的DLL拷贝到该项目bin下)
Console.WriteLine("--------------3. 利用接口进行改造----------------");
IPhone androidPhone3 = ObjectFactory.CreateObject<IPhone>();
androidPhone3.Call(); //总结:第三个阶段基本上已经是我们自己写容器的最高层次
//下面我们将借助微软的第三方容器Unity来创建 }
#endregion

依赖倒置原则详见:http://www.cnblogs.com/yaopengfei/p/7101347.html

手写IOC详见:http://www.cnblogs.com/yaopengfei/p/6891286.html

二. Unity深入浅出

01-Unity深入浅出(一)

(一). Unity介绍

 1. Unity的获取方式

   ①:下载网址:http://unity.codeplex.com/

②:通过Nuget获取  (目前最新版本为4.0.1)

01-Unity深入浅出(一)

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.  代码说明

   ①:代码结构(后面所有的代码说明都是基于这个结构的)

 详细说明:各个类的内容待写。

01-Unity深入浅出(一)

 ②:直接创建简单对象(需要添加对Interface层和Service层的引用)

  {
Console.WriteLine("------------------- 二. Unity入门 -------------------");
Console.WriteLine("------------------- 01-直接创建对象 -------------------");
IUnityContainer container = new UnityContainer();
//进行注册,可以是: 接口-类、父类-子类、抽象类-子类。
//container.RegisterType<IPhone, AndroidPhone>();
//也可以用实例注册已经存在的对象
AndroidPhone android = new AndroidPhone();
container.RegisterInstance<IPhone>(android);
//解析对象
IPhone phone = container.Resolve<IPhone>();
phone.Call();
//我们会发现,虽然注册了AndroidPhone对象,但是该对象中的三个属性并没有实例化,依旧为空
Console.WriteLine("phone.iHeadphone==null? {0}", phone.iHeadphone == null);
Console.WriteLine("phone.iMicrophone==null? {0}", phone.iMicrophone == null);
Console.WriteLine("phone.iPower==null? {0}", phone.iPower == null);
}

 详解:上述代码我们分别通过RegisterType注册类型、RegisterInstance注册已经存在的类型,我们会发现最终生成AndoridPhone中的三个属性并没有被实例化,那么怎么实例化里面的属性呢?这就涉及到后面的依赖注入了。

 ③. 指定命名来创建对象(需要添加对Interface层和Service层的引用)

    {
Console.WriteLine("------------------- 03-同时创建多个对象 -------------------");
//可以通过添加一个参数来区分
IUnityContainer container = new UnityContainer();
//注册类型时(指定命名)
container.RegisterType<IPhone, AndroidPhone>();
container.RegisterType<IPhone, ApplePhone>("apple");
container.RegisterType<IPhone, AndroidPhone>("android");
//依赖注入
container.RegisterType<IMicrophone, Microphone>();
container.RegisterType<IHeadphone, Headphone>();
container.RegisterType<IPower, Power>();
//解析对象
IPhone iphone1 = container.Resolve<IPhone>();
iphone1.Call();
//解析对象(指定命名)
IPhone iphone2 = container.Resolve<IPhone>("apple");
iphone2.Call();
IPhone iphone3 = container.Resolve<IPhone>("android");
iphone3.Call(); //获取所有已注册且指定命名的对象(未指定命名的不能获取)
var list = container.ResolveAll<IPhone>(); }

  详解:注册类型时,如果默认不指定命名,则后面的会覆盖前面的,解析多个对象时,永远是最后一个注册的类型的对象。  所以RegisterType指定命名 、Resolve解析对象指定命名广泛用于同时创建多个对象的情况。

  我们发现上述代码有三句是依赖注入的,这个在下面讲解。

(三). 依赖注入的三种形式

  Unity实现依赖注入有三种形式,分别是构造函数注入、属性注入、方法注入,分别对应三个特性:【InjectionConstructor】、【Dependency】、【InjectionMethod】,加上这三个特性表明构造函数中、属性中、方法中需要注入对象,使其实例化。

  方式一:属性注入。

  有WinPhone1类,实现了Iphone接口。其中WinPhone1类中的iMicrophone和iHeadphone两个属性上分别添加【Dependency】和【Dependency("fk")】,表明该属性对应的对象需要注入,其中iHeadphone需要Unity容器"指定命名"来注册类型。

  public interface IPhone
{
void Call();
IMicrophone iMicrophone { get; set; } IHeadphone iHeadphone { get; set; } IPower iPower { get; set; }
}
    /// <summary>
/// 测试属性注入的类
/// </summary>
public class WinPhone1 : IPhone
{
[Dependency("fk")] //表示该属性需要注入,且需要指定名称
public IMicrophone iMicrophone { get; set; }
[Dependency] //表示该属性需要注入
public IHeadphone iHeadphone { get; set; }
public IPower iPower { get; set; } public WinPhone1()
{
Console.WriteLine("{0}被构造", this.GetType().Name);
} public void Call()
{
Console.WriteLine("{0}打电话", this.GetType().Name); ;
}
}
                IUnityContainer container = new UnityContainer();
//方式一: 属性注入
Console.WriteLine("----------------------------方式一: 属性注入 -------------------------------");
container.RegisterType<IPhone, WinPhone1>();
//指定命名注入
container.RegisterType<IMicrophone, Microphone>("fk");
//普通注入
container.RegisterType<IHeadphone, Headphone>();
IPhone phone1 = container.Resolve<IPhone>();
phone1.Call();

01-Unity深入浅出(一)

补充说明:我们发现Microphone和HeadPhone均被构造,说明这两个类被注入成功。

  方式二:方法注入。

  有WinPhone2类,实现了Iphone接口。其中在WinPhone2中的方法Init1234方法上加【InjectionMethod】特性,表示该方法中有对象需要注入。

    public class WinPhone2:IPhone
{
public IMicrophone iMicrophone { get; set; }
public IHeadphone iHeadphone { get; set; }
public IPower iPower { get; set; } public WinPhone2()
{
Console.WriteLine("{0}被构造", this.GetType().Name);
} public void Call()
{
Console.WriteLine("{0}打电话", this.GetType().Name); ;
} [InjectionMethod] //方法注入,表明该方法中有对象需要注入
public void Init1234(IPower power)
{
this.iPower = power;
}
}
        //方式二:方法注入
Console.WriteLine("----------------------------方式二:方法注入 -------------------------------");
container.RegisterType<IPhone, WinPhone2>();
container.RegisterType<IPower, Power>();
IPhone phone2 = container.Resolve<IPhone>();
phone2.Call();

01-Unity深入浅出(一)

补充说明:我们发现Power被构造,说明该类被注入成功。

  方式三:构造函数注入(又分四种情况)

  A.  默认注入。 自动识别参数数量最多的构造函数,表示该构造函数中有对象需要注入。

  B. 指定构造函数。通过在构造函数上加 [InjectionConstructor]特性,表示该构造函数中有对象需要注入。

  C. 指定构造函数且指定注入参数的名称。通过在构造函数上加 [InjectionConstructor]特性,并且在参数上加 [Dependency("fk")] ,表示该构造函数中的该参数中的对象需要注入。

  D. 指定构造函数且指定注入参数的值(了解即可)。

     /// <summary>
/// 测试构造函数注入(默认方式)
/// </summary>
public class WinPhone3 : IPhone
{
public IMicrophone iMicrophone { get; set; }
public IHeadphone iHeadphone { get; set; }
public IPower iPower { get; set; } public WinPhone3(IPower iPower)
{
Console.WriteLine("{0}被构造", this.GetType().Name);
} public void Call()
{
Console.WriteLine("{0}打电话", this.GetType().Name); ;
} }
     /// <summary>
/// 测试构造函数注入(指定构造函数)
/// </summary>
public class WinPhone4 : IPhone
{
public IMicrophone iMicrophone { get; set; }
public IHeadphone iHeadphone { get; set; }
public IPower iPower { get; set; }
public WinPhone4()
{ }
[InjectionConstructor] //指定该构造函数需要被注入
public WinPhone4(IPower iPower)
{
Console.WriteLine("{0}被构造", this.GetType().Name);
} public void Call()
{
Console.WriteLine("{0}打电话", this.GetType().Name); ;
} }
     /// <summary>
/// 测试构造函数注入(指定参数依赖的注册名称)
/// </summary>
public class WinPhone5 : IPhone
{
public IMicrophone iMicrophone { get; set; }
public IHeadphone iHeadphone { get; set; }
public IPower iPower { get; set; }
public WinPhone5()
{ }
[InjectionConstructor] //指定该构造函数需要被注入且指定注入参数的名称
public WinPhone5([Dependency("fk")]IPower iPower)
{
Console.WriteLine("{0}被构造", this.GetType().Name);
} public void Call()
{
Console.WriteLine("{0}打电话", this.GetType().Name); ;
} }
   /// <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); ;
} }
                  //方式三:构造函数注入(又分4中情况)
/*
A. 默认方式
B. 指定构造函数
C. 指定参数依赖的注册名称
D. 指定参数值
*/
Console.WriteLine("-----------------------方式三:构造函数注入(又分4中情况)----------------------------");
//A. 默认注入参数最多的构造函数
{
Console.WriteLine("-----------------------A. 默认注入参数最多的构造函数----------------------------");
container.RegisterType<IPhone, WinPhone3>();
container.RegisterType<IPower, Power>();
IPhone iphone = container.Resolve<IPhone>();
iphone.Call();
}
//B. 指定构造函数
{
Console.WriteLine("-----------------------B. 指定构造函数----------------------------");
container.RegisterType<IPhone, WinPhone4>();
container.RegisterType<IPower, Power>();
IPhone iphone = container.Resolve<IPhone>();
iphone.Call();
}
//C. 指定参数依赖的注册名称
{
Console.WriteLine("-----------------------C. 指定参数依赖的注册名称----------------------------");
container.RegisterType<IPhone, WinPhone5>();
container.RegisterType<IPower, Power>("fk");
IPhone iphone = container.Resolve<IPhone>();
iphone.Call();
}
//D.指定参数值(了解即可)
{
Console.WriteLine("-----------------------D.指定参数值(了解即可)----------------------------");
container.RegisterType<IPhone, WinPhone6>(new InjectionConstructor(new Power() { }, "ypf"));
IPhone iphone = container.Resolve<IPhone>();
iphone.Call();
}

01-Unity深入浅出(一)

  小结一下: 以上内容分别介绍了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的强大。