一、概述
IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection)。
作用:将各层的对象以松耦合的方式组织在一起,解耦,各层对象的调用完全面向接口。当系统重构的时候,代码的改写量将大大减少。
依赖注入: 当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常有调用者来创建被调用者的实例。然而采用依赖注入的方式,创建被调用者的工作不再由调用者来完成,因此叫控制反转,创建被调用者的实例的工作由IOC容器来完成,然后注入调用者,因此也称为依赖注入。
Unity是微软Patterns & Practices团队所开发的一个轻量级的,并且可扩展的依赖注入(Dependency Injection)容器;
它支持常用的三种依赖注入方式:构造器注入(Constructor Injection)、属性注入(Property Injection),以及方法调用注入(Method Call Injection)。
二、简单示例
1、定义一个接口,封装数据库的基本CRUD操作,接口定义如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
/// <summary>
/// 数据访问接口
/// </summary>
public interface IDataBase
{
string Insert();
string Delete();
string Update();
string Query();
}
}
2、定义一个MSSQL类实现该接口,用来模仿SQLServer操作,MSSQL类定义如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public class DbMSSQL : IDataBase
{
public string Delete()
{
return "MSSQL执行删除";
}
public string Insert()
{
return "MSSQL执行插入";
}
public string Query()
{
return "MSSQL执行查询";
}
public string Update()
{
return "MSSQL执行更新";
}
}
}
3、定义一个Oracle类实现该接口,模仿Oracle的操作,Oracle类定义如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public class DbOracle : IDataBase
{
public string Delete()
{
return "MSSQL执行删除";
}
public string Insert()
{
return "MSSQL执行插入";
}
public string Query()
{
return "MSSQL执行查询";
}
public string Update()
{
return "MSSQL执行更新";
}
}
}
4、定义一个控制台应用程序来调用:
// 常规做法,即程序的上端,依赖于下端,依赖于细节
DbMSSQL mssql = new DbMSSQL();
// 通过抽象来依赖
IDataBase dbInterface = new DbMSSQL();
但是这样修改以后,虽然左边是抽象了,但是右边还是依赖于细节。
先来看看最简单的Unity实现方式:
// 常规做法,即程序的上端,依赖于下端,依赖于细节
DbMSSQL mssql = new DbMSSQL();
// 通过抽象来依赖
IDataBase dbInterface = new DbMSSQL();
IUnityContainer container = new UnityContainer();// 1. 定义一个空容器
container.RegisterType<IDataBase, DbMSSQL>();// 2.注册类型,表示遇到IDataBase,创建DbMSSQL的实例
var db = container.Resolve<IDataBase>();
Console.WriteLine(db.Insert());
Console.ReadKey();
5、定义一个控制台应用程序来调用:
三种注入方式:构造函数注入、属性注入、方法注入。
5.1 定义IHeadphone IMicrophone IPower 接口,代码如下:
public interface IHeadphone
{
}
public interface IMicrophone
{
}
public interface IPower
{
}
5.2 定义IPhone接口,代码如下:
public interface IPhone
{
void call();
IMicrophone iMicrophone { get; set; }
IHeadphone iHeadphone { get; set; }
IPower iPower { get; set; }
}
5.3 IPhone接口的实现如下:
public class ApplePhone : IPhone
{
[Dependency]//属性注入
public IMicrophone iMicrophone { get ; set; }
public IHeadphone iHeadphone { get ; set ; }
public IPower iPower { get; set ; }
[InjectionConstructor]//构造函数注入
public ApplePhone(IHeadphone headphone)
{
this.iHeadphone = headphone;
Console.WriteLine("{0}带参数构造函数",this.GetType().Name);
}
public void call()
{
Console.WriteLine("{0}打电话", this.GetType().Name);
}
[InjectionMethod]//方法注入
public void Init2(IPower power)
{
this.iPower = power;
}
}
5.4 分别实现上面定义的接口
public class Headphone:IHeadphone
{
public Headphone()
{
Console.WriteLine("Headphone 被构造");
}
}
public class Microphone:IMicrophone
{
public Microphone()
{
Console.WriteLine("Microphone 被构造");
}
}
public class Power:IPower
{
public Power()
{
Console.WriteLine("Power 被构造");
}
}
从输出结果中可以看出三种注入方式的执行顺序:先执行构造函数注入,在执行属性注入,最后执行方法注入。
注意:默认情况下如果构造函数上面没有使用特性,那么默认找参数最多的构造函数执行注入。
IUnityContainer container = new UnityContainer();//1、定义一个空容器
container.RegisterType<IDbInterface, DbMSSQL>();//2、注册类型,表示遇到IDbInterface的类型,创建DbMSSQL的实例
container.RegisterType<IDbInterface, DbOracle>();//表示遇到IDbInterface的类型,创建DbMSSQL的实例
var db = container.Resolve<IDbInterface>();
Console.WriteLine(db.Insert());
从运行结果中可以看出,后面注册的类型会把前面注册的类型给覆盖掉,那么该如何解决呢?可以通过参数的方式来解决,代码如下:
IUnityContainer container = new UnityContainer();//1、定义一个空容器
container.RegisterType<IDbInterface, DbMSSQL>("sql");//2、注册类型,表示遇到IDbInterface的类型,创建DbMSSQL的实例
container.RegisterType<IDbInterface, DbOracle>("oracle");//表示遇到IDbInterface的类型,创建DbMSSQL的实例
var sql = container.Resolve<IDbInterface>("sql");
var oracle = container.Resolve<IDbInterface>("oracle");
Console.WriteLine(sql.Insert());
Console.WriteLine(oracle.Insert());
6、生命周期
IUnityContainer container = new UnityContainer();
container.RegisterType<IDbInterface, DbMSSQL>();
IDbInterface db1 = container.Resolve<IDbInterface>();
IDbInterface db2 = container.Resolve<IDbInterface>();
Console.WriteLine("HashCode:"+db1.GetHashCode().ToString());
Console.WriteLine("HashCode:" + db2.GetHashCode().ToString());
Console.WriteLine(object.ReferenceEquals(db1,db2));
表明db1和db2是两个不同的实例,即默认情况下生命周期是瞬时的,每次都是创建一个新的实例。
container.RegisterType<IDbInterface, DbMSSQL>(new TransientLifetimeManager());表示是瞬时生命周期,默认情况下即这种。
IUnityContainer container = new UnityContainer();
container.RegisterType<IDbInterface, DbMSSQL>(new ContainerControlledLifetimeManager());
IDbInterface db1 = container.Resolve<IDbInterface>();
IDbInterface db2 = container.Resolve<IDbInterface>();
Console.WriteLine("HashCode:" + db1.GetHashCode().ToString());
Console.WriteLine("HashCode:" + db2.GetHashCode().ToString());
Console.WriteLine(object.ReferenceEquals(db1, db2));
db1和db2是同一个实例。
container.RegisterType<IDbInterface, DbMSSQL>(new ContainerControlledLifetimeManager())表示是容器单例,每次都是同一个实例。
7、使用配置文件实现
在上面的例子中,所有的例子都是一直在依赖于细节,那么怎么解决不依赖于细节呢?答案是只能使用配置文件,配置文件如下:
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
</configSections>
<unity>
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
<containers>
<container name="testContainer">
<!--逗号前面是接口类型的完全限定名:命名空间+接口名称,逗号后面是DLL文件的名称 name解决同一个接口不同实例问题-->
<register type="DataBase.Interface.IDbInterface,DataBase.Interface" mapTo="DataBase.MSSQL.DbMSSQL, DataBase.MSSQL" name="sql"/>
<register type="DataBase.Interface.IDbInterface,DataBase.Interface" mapTo="DataBase.Oracle.DbOracle, DataBase.Oracle" name="oracle"/>
</container>
</containers>
</unity>
</configuration>
注意:这个一个单独的配置文件,要把属性里面的复制到输出目录改为始终复制,那么这个配置文件才会生成到Debug目录里面。
引用里面只要对接口的引用了,没有对具体实现的引用。去掉了对细节的依赖。
注意:使用配置文件实现时,必须把接口的具体实现类复制到程序目录下面。
如果有额外添加了一种数据库,那么只需要修改配置文件,把新的实现类复制到程序目录下面即可实现程序的升级。
使用配置文件的时候,需要把UnityContainer容器定义为静态的,这样只需要读取一次配置文件即可。