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目录下)也是可以使用的,这样我们就完全不对底层模块的依赖,只依赖于抽象。
依赖注入方式
Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中将具体依赖注入划分为三种形式,即构造器注入、属性(设置)注入和接口注入,习惯将其划分为一种(类型)匹配和三种注入:
- 类型匹配(Type Matching):虽然我们通过接口(或者抽象类)来进行服务调用,但是服务本身还是实现在某个具体的服务类型中,这就需要某个类型注册机制来解决服务接口和服务类型之间的匹配关系;
- 构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象;
- 属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性;
- 方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。
我们创建一个控制台程序,定义如下几个接口(IA、IB、IC和ID)和它们各自的实现类(A、B、C、D)。在类型A中定义了3个属性B、C和D,其类型分别为接口IB、IC和ID。其中属性B在构在函数中被初始化,以为着它会以构造器注入的方式被初始化;属性C上应用了DependencyAttribute特性,意味着这是一个需要以属性注入方式被初始化的依赖属性;属性D则通过方法Initialize初始化,该方法上应用了特性InjectionMethodAttribute,意味着这是一个注入方法在A对象被IoC容器创建的时候会被自动调用。
public interface IA { } public interface IB { } public interface IC { } public interface ID { } public class A : IA { public IB B { get; set; } [Dependency] public IC C { get; set; } public ID D { get; set; } public A(IB b) { this.B = b; } [InjectionMethod] public void Initalize(ID d) { this.D = d; } } public class B : IB { } public class C : IC { } public class D : ID { }
然后我们为该应用添加一个配置文件,并定义如下一段关于Unity的配置。这段配置定义了一个名称为defaultContainer的Unity容器,并在其中完成了上面定义的接口和对应实现类之间映射的类型匹配。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> </configSections> <unity> <containers> <container name="defaultContainer"> <register type="UnityDemo.IA,UnityDemo" mapTo="UnityDemo.A, UnityDemo"/> <register type="UnityDemo.IB,UnityDemo" mapTo="UnityDemo.B, UnityDemo"/> <register type="UnityDemo.IC,UnityDemo" mapTo="UnityDemo.C, UnityDemo"/> <register type="UnityDemo.ID,UnityDemo" mapTo="UnityDemo.D, UnityDemo"/> </container> </containers> </unity> </configuration>
最后在Main方法中创建一个代表IoC容器的UnityContainer对象,并加载配置信息对其进行初始化。然后调用它的泛型的Resolve方法创建一个实现了泛型接口IA的对象。最后将返回对象转变成类型A,并检验其B、C和D属性是否是空
class Program { static void Main(string[] args) { UnityContainer container = new UnityContainer(); UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection; configuration.Configure(container, "defaultContainer"); A a = container.Resolve<IA>() as A; if (null!=a) { Console.WriteLine("a.B==null?{0}",a.B==null?"Yes":"No"); Console.WriteLine("a.C==null?{0}", a.C == null ? "Yes" : "No"); Console.WriteLine("a.D==null?{0}", a.D == null ? "Yes" : "No"); } } }
从如下给出的执行结果我们可以得到这样的结论:通过Resolve<IA>方法返回的是一个类型为A的对象,该对象的三个属性被进行了有效的初始化。这个简单的程序分别体现了接口注入(通过相应的接口根据配置解析出相应的实现类型)、构造器注入(属性B)、属性注入(属性C)和方法注入(属性D)
a.B == null ? No
a.C == null ? No
a.D == null ? No
付费内容,请联系本人QQ:1002453261
本文来自博客园,作者:明志德道,转载请注明原文链接:https://www.cnblogs.com/for-easy-fast/articles/12483274.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析