20181123_控制反转(IOC)和依赖注入(DI)

时间:2022-02-26 16:25:46

一.   控制反转和依赖注入:

  1. 控制反转的前提, 是依赖倒置原则, 系统架构时,高层模块不应该依赖于低层模块,二者通过抽象来依赖 (依赖抽象,而不是细节)
  2. 如果要想做到控制反转(IOC), 就必须要使用依赖注入(DI), 也就是说控制反转是要实现的目的, 而依赖注入是达到这种目的的一种技术手段
  3. DI依赖注入, 在构造对象时,可以自动的去初始化当前需要构造的这个对象所需要的资源, 将其依赖的所有资源自动全部初始化, 有三种形式,  通过构造函数注入,   通过属性注入   通过方法注入(依赖注入的三种方式)
  4. 有了依赖注入,才可能做到无限层级的依赖抽象,才能做到控制反转

二.   一个简单的容器雏形, 部分代码示例:

  a)  这里相当于UI, 可以看做是高层

 //全部都依赖细节
AndroidPhone phone = new AndroidPhone(); //这边的细节类, 可以看做是低层
//左边依赖于抽象, 但是右边还是在依赖细节
IPhone phone = new AndroidPhone();
//左边依赖于抽象, 右边交给工厂
// 高层本来是依赖低层,但是可以通过工厂(容器)来决定细节,去掉了对低层的依赖, 这就是IOC控制反转:把高层对低层的依赖,转移到第三方决定,避免高层对低层的直接依赖(是一种目的)
IPhone phone = ObjectFactory.CreatePhone();//容器的雏形

  b)  工厂这里相当于第三方

 public class ObjectFactory
{
/// <summary>
/// 简单工厂+配置文件+反射
/// </summary>
/// <returns></returns>
public static IPhone CreatePhone()
{
string classModule = ConfigurationManager.AppSettings["iPhoneType"];
Assembly assemly = Assembly.Load(classModule.Split(',')[]);
Type type = assemly.GetType(classModule.Split(',')[]);
return (IPhone)Activator.CreateInstance(type);
}
}
//配置文件中的内容:
<appSettings>
<add key="iPhoneType" value=" MyIOCTest.Service.ApplePhone, MyIOCTest.Service" />
</appSettings>

三.   使用Unity容器实现IOC

  a)  最简单的容器使用, 给一个接口注册一种类型:

 //最简单的Unity的使用演示, 给一个接口注册一种类型
Console.WriteLine("*************** Unity容器的初步应用***************");
//1 声明一个容器
IUnityContainer container = new UnityContainer();
//2. 初始化容器 注册类型 告诉容器, 如果遇到IPhone, 就帮我创建一个AndroidPhone的实例出来
container.RegisterType<IPhone, AndroidPhone>();
//3 容器内部通过反射创建对象
IPhone phone = container.Resolve<IPhone>();
phone.Call();

  b) 容器生成实例的各种方法 :在Unity容器中, 使用别名创建不同的子类

 //容器的基本使用; 使用别名创建子类
Console.WriteLine("***************1. Unity容器的初步应用; 使用别名创建不同的子类***************");
IUnityContainer container = new UnityContainer(); //只要碰到IPhone这个接口, 都用AndroidPhone这个实例来生成
container.RegisterType<IPhone, AndroidPhone>();//接口 container.RegisterType<AbstractPad, ApplePad>();//抽象类 接口和抽象类,都可以被实例化
//container.RegisterType<AbstractPad, ApplePadChild>();//这个会覆盖上面的, 如果不想覆盖, 就要用别名 //container.RegisterType<ApplePad, ApplePadChild>();//父子类 会覆盖<AbstractPad, ApplePad> 因为这个也是AbstractPad //上面的写法会被覆盖, 所有如果一个接口想生成多个子类的实例, 那么就应该在创建类型的时候, 使用别名, 象下面这样; 在创建的时候使用字符串别名
container.RegisterType<AbstractPad, ApplePad>("child");//1对多, 一个抽象类对应多个子类
container.RegisterType<AbstractPad, ApplePadChild>("grandchild");//1对多 //只要碰到ITV在这个接口, 都使用AppleTV(123)来创建实例, 这种写法跟直接使用细节, 没有什么区别了.
container.RegisterInstance<ITV>(new AppleTV());//注册实例; 不常用,依赖细节 IPhone phone = container.Resolve<IPhone>();
AbstractPad pad = container.Resolve<AbstractPad>();
ApplePad applePad = container.Resolve<ApplePad>(); //调用方法; 调用的时候也要传入别名字符串, 要不会分不清创建的那个类实例
var childPad = container.Resolve<AbstractPad>("child");
var grandchildPad = container.Resolve<AbstractPad>("grandchild");
var tv = container.Resolve<ITV>();

  c) 多层依赖, 依次注入,下面的代码演示, 当需要构造一个ApplePhone的时候, 这个ApplePhone所需要的资源都会被依次的自动注入

i.  ApplePhone代码如下:

 /// <summary>
/// 1. 在构造本类的时候, 优先调用带有[InjectionConstructor]特性的构造函数
/// 2. 容器在完成构造函数之后, 会检查当前类的所有属性(public的), 如果当前属性带有[Dependency]这个特性, 容器就会对其进行构造
/// 3. 构造完 构造函数和属性 之后, 容器开始检查方法, 如果方法是public 且上面添加了[InjectionMethod]特性, 容器也会帮其构造出来
/// </summary>
public class ApplePhone : IPhone
{
[Dependency]//二. 属性注入: 对容器有依赖
public IMicrophone iMicrophone { get; set; }
public IHeadphone iHeadphone { get; set; }
public IPower iPower { get; set; } /// <summary>
/// 这个属性没有添加[Dependency]特性, 所以不会被构造
/// </summary>
public ITV iTV { get; set; } //[InjectionConstructor]
public ApplePhone()
{
Console.WriteLine("{0}构造函数", this.GetType().Name);
} //[InjectionConstructor]
//一. 构造函数注入:最好的, 在没有[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 Init1234(IPower power)
{
this.iPower = power;
} /// <summary>
/// 如果一个方法不添加 [InjectionMethod]特性, 那么它在初始化的时候, 则不会被容器构造
/// </summary>
/// <param name="tV"></param>
public void InitITV(ITV tV)
{
this.iTV = tV;
}
}

    ii) 构造代码如下

 //多层依赖, 依次注入
{
//在构造对象时,可以自动的去初始化,对象需要的对象
//构造函数注入 属性注入 方法注入(依赖注入的三种方式)//多层的依赖注入, 必须逐层都要注入
//不管是构造对象,还是注入对象,这里都是靠反射做到的
Console.WriteLine("***************多层依赖, 依次注入***************");
IUnityContainer container = new UnityContainer();
container.RegisterType<IPhone, ApplePhone>();
container.RegisterType<IMicrophone, Microphone>();
container.RegisterType<IPower, Power>();
container.RegisterType<IHeadphone, Headphone>();
container.RegisterType<IBaseDAL, BaseDAL>();
IPhone phone = container.Resolve<IPhone>();
phone.Call();
}

  d) 生命周期管理; 不通过容器创建的对象, 在脱离作用域之后,这个时候没有任何引用的情况下, 就会被标记为垃圾, 等待GC回收; 在Unity中, 由于容器成了对象创建的入口, 所以可以加入自己对创建出来对象的管理逻辑;

 //使用Unity创建对象的时候, 默认每次创建都是一个全新的对象; 如果想要全局实现唯一(单例), 就要使用参数new ContainerControlledLifetimeManager()来让容器单例创建
IUnityContainer container = new UnityContainer();
container.RegisterType<IPhone, AndroidPhone>();//使用容器创建对象时, 默认的是瞬时生命周期, 也就是每次创建都是一个全新的对象
container.RegisterType<IPhone, AndroidPhone>(new TransientLifetimeManager());//Unity中默认创建的就是瞬时对象, 在构造的时候有没有 new TransientLifetimeManager() 都是一样
var phone1 = container.Resolve<IPhone>();
var phone2 = container.Resolve<IPhone>();
Console.WriteLine(object.ReferenceEquals(phone1, phone2)); //false; 创建的是两个不同的对象 //容器实现的单例
container.RegisterType<IPhone, AndroidPhone>(new ContainerControlledLifetimeManager());
var phone3 = container.Resolve<IPhone>();
var phone4 = container.Resolve<IPhone>();
Console.WriteLine(object.ReferenceEquals(phone3, phone4));//由于单例, 创建的是相同的对象 // 线程单例: 相同线程的实例相同, 不同线程的实例不同 应用场景:web请求 / 多线程操作
container.RegisterType<IPhone, AndroidPhone>(new PerThreadLifetimeManager()); IPhone iphone1 = null;
Action act1 = new Action(() =>
{ //使用单独的线程初始化IPhone1
iphone1 = container.Resolve<IPhone>();
Console.WriteLine($"iphone1由线程id={Thread.CurrentThread.ManagedThreadId}");
}); var result1 = act1.BeginInvoke(null, null); IPhone iphone2 = null;
Action act2 = new Action(() =>
{
//单独线程初始化iphone2
iphone2 = container.Resolve<IPhone>();
Console.WriteLine($"iphone2由线程id={Thread.CurrentThread.ManagedThreadId}");
}); IPhone iphone3 = null;
var result2 = act2.BeginInvoke(t => //BeginInvoke是回调线程, 这将会和上面的Action act2的线程属于同一个线程, 注意BeginInvoke是当当前线程完成后, 再用当前的线程来执行现在的任务; 也就是act2完成会后, 再执行这里面的
{
iphone3 = container.Resolve<IPhone>();
Console.WriteLine($"iphone3由线程id={Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"object.ReferenceEquals(iphone2, iphone3)={object.ReferenceEquals(iphone2, iphone3)}"); //结果为True
}, null); act1.EndInvoke(result1); //调用EndInvoke获得执行解脱
act2.EndInvoke(result2); Console.WriteLine($"object.ReferenceEquals(iphone1, iphone2)={object.ReferenceEquals(iphone1, iphone2)}");//结果为false //了解知识:
//关于单例还可以使用分级容器来创建; 不同子容器创建的单例属于不同的实例, 相同子容器创建的单例对象属于同一个实例对象
container.RegisterType<IPhone, AndroidPhone>(new HierarchicalLifetimeManager());//分级容器单例
//获取子容器
IUnityContainer childContainer = container.CreateChildContainer(); //外部可释放单例 单例是全局唯一;一旦释放大家都没了;
container.RegisterType<IPhone, AndroidPhone>(new ExternallyControlledLifetimeManager()); //当真的发生了循环使用可以使用这个来创建 不推荐; 不要使用;
container.RegisterType<IPhone, AndroidPhone>(new PerResolveLifetimeManager());

四.  摆脱细节, 使用配置文件实现IOC:

a) Unity.Config配置文件代码:

 <configuration>
<!--配置文件的路径到底应该放到哪个目录下: 配置文件最好是写到项目里, 而不是写到类库中; 始终复制-->
<!--当所有的引用都被移除之后, 注意一定要复制对应dll文件到当前程序运行目录下-->
<configSections>
<!--固定写法; -->
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
</configSections>
<unity>
<!--AOP扩展-->
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
<containers> <!--容器集合, 存放一个个container容器, 所有的容器名称不能重复-->
<container name="testContainer">
<!--type 使用逗号分隔, 前面是完整的接口名称, 后面是接口所在的dll文件名 -->
<!--mapTo 表示映射名称, 想生成那个类, 就在这里配置一下; 当前这一行是想利用IPhone来生成ApplePhone这个类; name="Android" 表示别名-->
<register type="MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.ApplePhone, MyIOCTest.Service"/>
<register type=" MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.AndroidPhone, MyIOCTest.Service" name="Android"/>
<register type=" MyIOCTest.Interface.IMicrophone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Microphone, MyIOCTest.Service"/>
<register type=" MyIOCTest.Interface.IHeadphone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Headphone, MyIOCTest.Service"/>
<register type=" MyIOCTest.Interface.IPower, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Power, MyIOCTest.Service"/>
<register type=" MyIOCTest.IDAL.IBaseDAL, MyIOCTest.IDAL" mapTo=" MyIOCTest.DAL.BaseDAL, MyIOCTest.DAL"/>
</container>
</containers>
</unity>
</configuration>

  b)  实现代码, 程序运行必须是依赖细节的, 但是编码过程又不想依赖细节, 所以只能通过配置文件:

  // ExeConfigurationFileMap  配置文件操作对象
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
//配置文件的路径到底应该放到哪个目录下: 配置文件最好是写到项目里, exe所在的目录, 而不是写到类库中; 注意始终复制
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"); //testContainer 容器的名称
IPhone phone = container.Resolve<IPhone>();
phone.Call(); //使用别名构造
IPhone android = container.Resolve<IPhone>("Android");
android.Call(); //4.1 使用配置文件的时候, 不需要依赖细节类, 只需要引用接口的dll即可; 但是要注意的是, 还是必须要将细节类的 dll复制 到项目运行的根目录下的; 要不然就会报错

五.   AOP和IOC的组合使用:

  a)   配置文件代码:

 <!--Unity 5.8.6
Unity.Interception 5.5.3 配置文件中的dll名称改了
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
改为
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/>
改为
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
--> <configuration>
<!--配置文件的路径到底应该放到哪个目录下: 配置文件最好是写到项目里, 而不是写到类库中; 始终复制-->
<!--当所有的引用都被移除之后, 注意一定要复制对应dll文件到当前程序运行目录下-->
<configSections>
<!--固定写法; -->
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
</configSections>
<unity>
<!--AOP扩展-->
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
<containers> <!--容器集合, 里面放着一个个container容器-->
<!--这下面有一个个容器, 所有的容器名称不能重复--> <!--name表示容器的名字, 前端使用的时候要传入-->
<container name="testContainerAOP">
<extension type="Interception"/>
<register type=" MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.AndroidPhone, MyIOCTest.Service.Extend">
<interceptor type="InterfaceInterceptor"/> <!--加入AOP扩展, 访问AndroidPhone的时候, 执行AOP-->
<!--权限认证-->
<interceptionBehavior type=" MyIOCTest.Framework.AOP.AuthorizeBehavior, MyIOCTest.Framework"/>
<!--发送短信-->
<interceptionBehavior type=" MyIOCTest.Framework.AOP.SmsBehavior, MyIOCTest.Framework"/>
<!--异常捕获-->
<interceptionBehavior type=" MyIOCTest.Framework.AOP.ExceptionLoggingBehavior, MyIOCTest.Framework"/>
<!--缓存-->
<interceptionBehavior type=" MyIOCTest.Framework.AOP.CachingBehavior, MyIOCTest.Framework"/>
<!--调用前写日志-->
<interceptionBehavior type=" MyIOCTest.Framework.AOP.LogBeforeBehavior, MyIOCTest.Framework"/>
<!--参数检查-->
<interceptionBehavior type=" MyIOCTest.Framework.AOP.ParameterCheckBehavior, MyIOCTest.Framework"/>
<!--调用后写日志-->
<interceptionBehavior type=" MyIOCTest.Framework.AOP.LogAfterBehavior, MyIOCTest.Framework"/>
</register>
<register type=" MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.AndroidPhone, MyIOCTest.Service.Extend" name="Android"/>
<register type=" MyIOCTest.Interface.IMicrophone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Microphone, MyIOCTest.Service.Extend"/>
<register type=" MyIOCTest.Interface.IHeadphone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Headphone, MyIOCTest.Service.Extend"/>
<register type=" MyIOCTest.Interface.IPower, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Power, MyIOCTest.Service.Extend"/>
<register type=" MyIOCTest.IDAL.IBaseDAL, MyIOCTest.IDAL" mapTo=" MyIOCTest.DAL.BaseDAL, MyIOCTest.DAL">
</register>
</container>
</containers>
</unity>
</configuration>

  b)  调用示例:

 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, "testContainerAOP"); //AOP的容器扩展
IPhone phone = container.Resolve<IPhone>();
phone.Call(); IPhone android = container.Resolve<IPhone>("Android");
android.Call();

相关文章