IOC Unity
IOC Unity
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
依赖倒置原则:
A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
B.抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
Unity是微软patterns & practices组用C#实现的轻量级,可扩展的依赖注入容器,它为方便开发者建立松散耦合的应用程序。
引言
在我们的工作中经常会用到三层架构。UI层复杂界面;BLL层复杂业务逻辑;DLL复杂数据库操作。调用方式为 UI层调用BLL层,BLL层调用DLL层,然后为了抽象,我们会添加一个IBLL层与IDLL层,分别为BLL和DLL的抽象。
如下代码:
IBLL层代码
namespace IBLL
{
public interface IPhone
{
void Call();
}
}
BLL层代码
namespace BLL
{
public class ApplePhone : IPhone
{
public void Call()
{
Console.WriteLine("ApplePhone Call!");
}
}
}
BLL层代码
namespace BLL
{
public class AndroidPhone : IPhone
{
public void Call()
{
Console.WriteLine("AndroidPhone Call!");
}
}
}
界面层:
IPhone phone = new ApplePhone();
phone.Call();
但是这样调用,会让界面依赖于实现层。这种调用方式违背了依赖倒置原则。
当然我们也可以使用工厂模式+配置文件的方式进行优化。
不过我们这里介绍一个更好用的东西 Unity 容器。
Unity容器的初步应用
添加Unity包
右击引用-->管理NuGet程序包-->搜索Unity-->安装。(使用的为vs2017,不过过程基本类似)
使用
基本方式
IUnityContainer unityContainer = new UnityContainer();//声明一个容器
unityContainer.RegisterType<IPhone, ApplePhone>();//将IPhone与ApplePhone进行映射
IPhone phone = unityContainer.Resolve<IPhone>();//反射创建对象
phone.Call();//调用方法
//输出:ApplePhone Call!
别名方式
IUnityContainer unityContainer = new UnityContainer();
unityContainer.RegisterType<IPhone, ApplePhone>("Apple");//设置别名
unityContainer.RegisterType<IPhone, AndroidPhone>("Android");
IPhone phone1 = unityContainer.Resolve<IPhone>("Apple");//通过别名 创建对象
phone1.Call();//输出:ApplePhone Call!
IPhone phone2 = unityContainer.Resolve<IPhone>("Android");
phone2.Call();//输出:AndroidPhone Call!
依赖注入,多层架构
现在我们再添加一些代码:
IBLL层
namespace IBLL
{
public interface IPower
{
}
}
BLL层
namespace BLL
{
public class Power : IPower
{
}
}
namespace BLL
{
public class WinPhone : IPhone
{
public WinPhone()
{
Console.WriteLine("WinPhone 无参数构造函数");
}
public WinPhone(IPower power)
{
Console.WriteLine("WinPhone IPower参数构造函数");
}
public void Call()
{
Console.WriteLine("WinPhone Call!");
}
}
}
我们对代码做了如下的修改:
- 给IBLL层添加了一个IPower接口
- 在BLL层的类Power中实现了它
- 添加了一个实现IPhone接口的WinPhone类,并给这个类创建了一个无参数的构造函数和一个有一个参数的构造函数。
然后我们在界面层调用
IUnityContainer unityContainer = new UnityContainer();
unityContainer.RegisterType<IPhone, WinPhone>();
var phone = unityContainer.Resolve<IPhone>();
phone.Call();
在调用时系统抛出一个异常:
Resolution of the dependency failed, type = 'IBLL.IPhone', name = '(none)'.
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The current type, IBLL.IPower, is an interface and cannot be constructed. Are you missing a type mapping?
-----------------------------------------------
At the time of the exception, the container was:
Resolving BLL.WinPhone,(none) (mapped from IBLL.IPhone, (none))
Resolving parameter 'power' of constructor BLL.WinPhone(IBLL.IPower power)
Resolving IBLL.IPower,(none)
大致意思为缺少IBLL.IPower的映射。
我们再次修改界面层的调用:
IUnityContainer unityContainer = new UnityContainer();
unityContainer.RegisterType<IPhone, WinPhone>();
unityContainer.RegisterType<IPower, Power>();//添加接口IPower的映射
var phone = unityContainer.Resolve<IPhone>();
phone.Call();
/*
输出:
WinPhone IPower参数构造函数
WinPhone Call!
*/
我们不难发现创建的WinPhone对象时所使用的构造函数为 带有参数的构造函数,并且通过之前配置的映射将参数也实例化了。
生命周期管理
IUnityContainer container = new UnityContainer();
container.RegisterType<IPhone, AndroidPhone>();//默认 瞬时 每一次创建的实例对象都是新的
container.RegisterType<IPhone, AndroidPhone>(new TransientLifetimeManager());//默认 瞬时 每一次创建的实例对象都是新的
container.RegisterType<IPhone, AndroidPhone>(new ContainerControlledLifetimeManager());//容器单例 每一次的对象都是一样的相当于单例模式。单例就不要自己实现
container.RegisterType<IPhone, AndroidPhone>(new PerThreadLifetimeManager());//线程单例:相同线程的实例相同 不同线程的实例不同 web请求/多线程操作
配置文件
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config");//找配置文件的路径
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
IUnityContainer container = new UnityContainer();
section.Configure(container, "testContainer");
IPhone phone = container.Resolve<IPhone>("Apple");
phone.Call();
IPhone android = container.Resolve<IPhone>("Android");
android.Call();
IPhone winPhone = container.Resolve<IPhone>("Win");
winPhone.Call();
/*
输出:
ApplePhone Call!
AndroidPhone Call!
WinPhone IPower参数构造函数
WinPhone Call!
*/
xml 如下。格式不用修改,只需要修改相应的容器和添加register标签
<?xml version="1.0" encoding="utf-8" ?>
<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"><!--这个是容器-->
<register type="IBLL.IPhone,IBLL" mapTo="BLL.AndroidPhone, BLL" name="Android"/> <!--name表示别名-->
<register type="IBLL.IPhone,IBLL" mapTo="BLL.ApplePhone, BLL" name="Apple"/>
<register type="IBLL.IPhone,IBLL" mapTo="BLL.WinPhone, BLL" name="Win"/>
<register type="IBLL.IPower,IBLL" mapTo="BLL.Power, BLL" />
</container>
</containers>
</unity>
</configuration>
添加完毕这些之后,我们可以将BLL的引用从项目中删除(但是需要将BLL.dll拷贝到bin目录下)也是可以使用的,这样我们就完全不对底层模块的依赖,只依赖于抽象。