使用Unity 实现依赖注入

什么是Unity?

Unity是一个轻量级的可扩展的依赖注入容器,支持构造函数,属性和方法调用注入。Unity可以处理那些从事基于组件的软件工程的开发人员所面对的问题。构建一个成功应用程序的关键是实现非常松散的耦合设计。松散耦合的应用程序更灵活,更易于维护。这样的程序也更容易在开发期间进行测试。你可以模拟对象,具有较强的具体依赖关系的垫片(轻量级模拟实现),如数据库连接,网络连接,ERP连接,和丰富的用户界面组件。例如,处理客户信息的对象可能依赖于其他对象访问的数据存储,验证信息,并检查该用户是否被授权执行更新。依赖注入技术,可确保客户类正确实例化和填充所有这些对象,尤其是在依赖可能是抽象的 。

关于IoC/DI
所谓控制反转(IoC: Inversion Of Control)就是应用本身不负责依赖对象的创建和维护,而交给一个外部容器来负责。这样控制权就由应用转移到了外部IoC容器,控制权就实现了所谓的反转。比如,在类型A中需要使用类型B的实例,而B实例的创建并不由A来负责,而是通过外部容器来创建。

有时我们又将IoC成为依赖注入(DI: Dependency Injection)。所谓依赖注入,就是由外部容器在运行时动态地将依赖的对象注入到组件之中。具体的依赖注入方式又包括如下三种典型的形式。

构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前会自定义创建相应参数对象;
属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性;
方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。

构造器注入

IPeople.cs

namespace UnityDemo
{
    public interface IPeople
    {
        void DrinkWater();
    }
}
 

VillagePeople.cs

using System;

namespace UnityDemo
{
    public class VillagePeople : IPeople
    {
        IWaterTool _pw;

        public VillagePeople(IWaterTool pw)
        {
            _pw = pw;
        }
        public void DrinkWater()
        {
            Console.WriteLine(_pw.returnWater());
        }
    }
}
复制代码

IWaterTool.cs

namespace UnityDemo
{
    public interface IWaterTool
    {
        string returnWater();
    }
}
 

PressWater.cs

namespace UnityDemo
{
    public class PressWater : IWaterTool
    {
        public string returnWater()
        {
            return "地下水好甜啊!!!";
        }
    }
}

Program.cs

using Microsoft.Practices.Unity;

namespace UnityDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            UnityContainer container = new UnityContainer();                          // 创建容器
            container.RegisterType<UnityDemo.IWaterTool, UnityDemo.PressWater>();     // 注册依赖对象
            UnityDemo.IPeople people = container.Resolve<UnityDemo.VillagePeople>();  // 返回调用者
            people.DrinkWater();                                                      // 喝水

        }
    }

    // 构造器注入
    // 构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。
    // 如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象。
    // RegisterType:可以看做是自来水厂决定用什么作为水源,可以是水库或是地下水,我只要“注册”开关一下就行了。
    // Resolve:可以看做是自来水厂要输送水的对象,可以是农村或是城市,我只要“控制”输出就行了。


}

Dependency属性注入

属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性。

VillagePeople.cs

using Microsoft.Practices.Unity;
using System;

namespace UnityDemo
{
    public class VillagePeople : IPeople
    {
        // 属性注入只需要在属性字段前面加[Dependency]标记就行了
        [Dependency]
        public IWaterTool _pw { get; set; }

        public void DrinkWater()
        {
            Console.WriteLine(_pw.returnWater());
        }
    }
} 

调用方式和构造器注入一样,通过

RegisterType< UnityDemo.IWaterTool, UnityDemo.PressWater>();注入就可以了,

除了使用RegisterType方法注册,我们还可以在配置文件中注册,[Dependency]和RegisterType方式其实都会产生耦合度,我们要添加一个属性或是修改一中注册都会去修改代码,我们要做的就是代码不去修改,只要修改配置文件了,这个在下面有讲解,这边就不多说,我们先看下使用UnityConfigurationSection的Configure方法加载配置文件注册:

Program.cs

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using System.Configuration;

namespace UnityDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            UnityContainer container = new UnityContainer(); // 创建容器
            UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName);
            configuration.Configure(container, "defaultContainer");
            IPeople people = container.Resolve<IPeople>(); // 返回调用者
            people.DrinkWater(); // 喝水

        }
    }
}
 

app.config

 
<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
             Microsoft.Practices.Unity.Configuration" />
  </configSections>
  <unity>
  <containers>
    <container name="defaultContainer">
      <register type="UnityContainerDemo.IPeople, UnityContainerDemo" mapTo="UnityContainerDemo.VillagePeople01, UnityContainerDemo">
        <lifetime type="singleton" />
      </register>
      <register type="UnityContainerDemo.IWaterTool, UnityContainerDemo" mapTo="UnityContainerDemo.PressWater, UnityContainerDemo"/>
    </container>
  </containers>
  </unity>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
  </startup>
</configuration>

 

InjectionMethod方法注入

方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。

方法注入和属性方式使用一样,方法注入只需要在方法前加[InjectionMethod]标记就行了,从方法注入的定义上看,只是模糊的说对某个方法注入,并没有说明这个方法所依赖的对象注入,所依赖的对象无非就三种:参数、返回值和方法内部对象引用,我们做一个示例试下:

 VillagePeople03.cs

复制代码
using Microsoft.Practices.Unity;

namespace UnityDemo
{
    class VillagePeople03 : IPeople
    {
        public IWaterTool tool;   // 我是对象引用
        public IWaterTool tool2;  // 我是参数
        public IWaterTool tool3;  // 我是返回值

        [InjectionMethod]
        public void DrinkWater()
        {
            if (tool == null) { }
        }

        [InjectionMethod]
        public void DrinkWater2(IWaterTool tool2)
        {
            this.tool2 = tool2;
        }

        public IWaterTool DrinkWater3()
        {
            return tool3;
        }
    }
} 

调用代码:

   public static void FuTest03()
        {
            UnityContainer container = new UnityContainer(); // 创建容器
            UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName);
            configuration.Configure(container, "defaultContainer");
            VillagePeople03 people = container.Resolve<IPeople>() as VillagePeople03;//返回调用者
            Console.WriteLine("people.tool == null(引用) ? {0}", people.tool == null ? "Yes" : "No");
            Console.WriteLine("people.tool2 == null(参数) ? {0}", people.tool2 == null ? "Yes" : "No");
            Console.WriteLine("people.tool3 == null(返回值) ? {0}", people.tool3 == null ? "Yes" : "No");

        } 

container.Resolve<IPeople>() as VillagePeople03;其实多此一举,因为已经在配置文件注册过了,不需要再进行转化,这边只是转化只是方便访问VillagePeople03对象的几个属性值,我们看下运行效果:

 

结果不言而喻,其实我们理解的方法注入就是对参数对象的注入,从typeConfig节点-method节点-param节点就可以看出来只有参数的配置,而并没有其他的配置,关于typeConfig下面会讲到。

非泛型注入

除了我们上面使用RegisterType和Resolve泛型方法,我们也可以使用非泛型注入,代码如下:

        public static void FuTest04()
        {
            UnityContainer container = new UnityContainer();//创建容器
            container.RegisterType(typeof(IWaterTool), typeof(PressWater));//注册依赖对象
            IPeople people = (IPeople)container.Resolve(typeof(VillagePeople));//返回调用者
            people.DrinkWater();//喝水

运行效果:

 

标识键

  我们知道,Unity提供了对象的容器,那么这个容器是如何进行索引的呢?也就是说,容器内的单元是如何标识的呢?在Unity中,标识主要有两种方式, 一种是直接使用接口(或者基类)作为标识键,另一种是使用接口(或者基类)与名称的组合作为标识键,键对应的值就是具体类。

 第一种使用接口(或者基类)作为标识键:

container.RegisterType<IWaterTool, PressWater>();

代码中的IWaterTool就是作为标识键,你可以可以使用基类或是抽象类作为标示,获取注册对象:container.Resolve<IWaterTool>(),如果一个Ioc容器容器里面注册了多个接口或是基类标示,我们再这样获取就不知道注册的是哪一个?怎么解决,就是用接口或是基类与名称作为标识键,示例代码如下:

        public static void FuTest05()
        {
            UnityContainer container = new UnityContainer();//创建容器
            container.RegisterType<IWaterTool, PressWater>("WaterTool1");//注册依赖对象WaterTool1
            container.RegisterType<IWaterTool, PressWater>("WaterTool2");//注册依赖对象WaterTool2
            IWaterTool wt = container.Resolve<IWaterTool>("WaterTool1");//返回依赖对象WaterTool1
            var list = container.ResolveAll<IWaterTool>();//返回所有注册类型为IWaterTool的对象
        }

  我们只需要在泛型方法RegisterType传入一个名称就可以来区分了(和注册接口或基类),获取的话也只要传入注册时候的名称即可,我们看下list中的集合对象:

ContainerControlledLifetimeManager单例

为了实现单例模式,我们通常的做法是,在类中定义一个方法如GetInstance,判断如果实例为null则新建一个实例,否则就返回已有实例。但是我觉得这种做法将对象的生命周期管理与类本身耦合在了一起。所以我觉得遇到需要使用单例的地方,应该将生命周期管理的职责转移到对象容器Ioc上,而我们的类依然是一个干净的类,使用Unity创建单例代码:

        public static void FuTest07()
        {
            UnityContainer container = new UnityContainer();//创建容器
            container.RegisterType<IWaterTool, PressWater>(new ContainerControlledLifetimeManager());//注册依赖对象
            IPeople people = container.Resolve<VillagePeople>();//返回调用者
            people.DrinkWater();//喝水
        }

上面演示了将IWaterTool注册为PressWater,并声明为单例,

ContainerControlledLifetimeManager字面意思上就是Ioc容器管理声明周期,我们也可以不使用类型映射,将某个类注册为单例: 

container.RegisterType<PressWater>(new ContainerControlledLifetimeManager());

  除了将类型注册为单例,我们也可以将已有对象注册为单例,使用RegisterInstance方法,示例代码: 

PressWater pw = new PressWater();
container.RegisterInstance<IWaterTool>(pw);

上面的代码就表示将PressWater的pw对象注册到Ioc容器中,并声明为单例。

  如果我们在注册类型的时候没有指定ContainerControlledLifetimeManager对象,Resolve获取的对象的生命周期是短暂的,Ioc容器并不会保存获取对象的引用,就是说我们再次Resolve获取对象的时候,获取的是一个全新的对象,如果我们指定ContainerControlledLifetimeManager,类型注册后,我们再次Resolve获取的对象就是上次创建的对象,而不是再重新创建对象,这也就是单例的意思。

猜你喜欢

转载自www.linuxidc.com/Linux/2016-04/130563.htm