zoukankan      html  css  js  c++  java
  • 20181123_控制反转(IOC)和依赖注入(DI)

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

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

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

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

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

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

     1 public class ObjectFactory
     2     {
     3         /// <summary>
     4         /// 简单工厂+配置文件+反射
     5         /// </summary>
     6         /// <returns></returns>
     7         public static IPhone CreatePhone()
     8         { 
     9             string classModule = ConfigurationManager.AppSettings["iPhoneType"];
    10             Assembly assemly = Assembly.Load(classModule.Split(',')[1]);
    11             Type type = assemly.GetType(classModule.Split(',')[0]);
    12             return (IPhone)Activator.CreateInstance(type);
    13         }
    14     }
    15     //配置文件中的内容:
    16       <appSettings>
    17         <add key="iPhoneType" value=" MyIOCTest.Service.ApplePhone, MyIOCTest.Service" />
    18         </appSettings>
    View Code

    三.   使用Unity容器实现IOC

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

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

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

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

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

                 i.  ApplePhone代码如下:

     1 /// <summary>
     2     /// 1. 在构造本类的时候, 优先调用带有[InjectionConstructor]特性的构造函数
     3     /// 2. 容器在完成构造函数之后, 会检查当前类的所有属性(public的), 如果当前属性带有[Dependency]这个特性, 容器就会对其进行构造
     4     /// 3. 构造完 构造函数和属性 之后, 容器开始检查方法, 如果方法是public 且上面添加了[InjectionMethod]特性, 容器也会帮其构造出来
     5     /// </summary>
     6     public class ApplePhone : IPhone
     7     {
     8         [Dependency]//二. 属性注入: 对容器有依赖
     9         public IMicrophone iMicrophone { get; set; }
    10         public IHeadphone iHeadphone { get; set; }
    11         public IPower iPower { get; set; }
    12 
    13         /// <summary>
    14         /// 这个属性没有添加[Dependency]特性, 所以不会被构造
    15         /// </summary>  
    16         public ITV iTV { get; set; }
    17 
    18         //[InjectionConstructor]
    19         public ApplePhone()
    20         {
    21             Console.WriteLine("{0}构造函数", this.GetType().Name);
    22         }
    23 
    24         //[InjectionConstructor]
    25         //一. 构造函数注入:最好的, 在没有[InjectionConstructor] 特性的时候,默认找参数最多的构造函数;
    26         public ApplePhone(IHeadphone headphone)
    27         {
    28             this.iHeadphone = headphone;
    29             Console.WriteLine("{0}带参数构造函数", this.GetType().Name);
    30         }
    31 
    32         public void Call()
    33         {
    34             Console.WriteLine("{0}打电话", this.GetType().Name); 
    35         }
    36 
    37         [InjectionMethod]//三. 方法注入(最不推荐的方法注入):最不好的,增加一个没有意义的方法,破坏封装
    38         public void Init1234(IPower power)
    39         {
    40             this.iPower = power;
    41         }
    42 
    43         /// <summary>
    44         /// 如果一个方法不添加 [InjectionMethod]特性, 那么它在初始化的时候, 则不会被容器构造
    45         /// </summary>
    46         /// <param name="tV"></param>
    47         public void InitITV(ITV tV)
    48         {
    49             this.iTV = tV;
    50         }
    51     }
    View Code

        ii) 构造代码如下

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

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

     1 //使用Unity创建对象的时候, 默认每次创建都是一个全新的对象; 如果想要全局实现唯一(单例), 就要使用参数new ContainerControlledLifetimeManager()来让容器单例创建
     2                 IUnityContainer container = new UnityContainer();
     3                 container.RegisterType<IPhone, AndroidPhone>();//使用容器创建对象时, 默认的是瞬时生命周期, 也就是每次创建都是一个全新的对象
     4                 container.RegisterType<IPhone, AndroidPhone>(new TransientLifetimeManager());//Unity中默认创建的就是瞬时对象, 在构造的时候有没有 new TransientLifetimeManager() 都是一样
     5                 var phone1 = container.Resolve<IPhone>();
     6                 var phone2 = container.Resolve<IPhone>();
     7                 Console.WriteLine(object.ReferenceEquals(phone1, phone2)); //false; 创建的是两个不同的对象
     8 
     9 
    10                 //容器实现的单例 
    11                 container.RegisterType<IPhone, AndroidPhone>(new ContainerControlledLifetimeManager());
    12                 var phone3 = container.Resolve<IPhone>();
    13                 var phone4 = container.Resolve<IPhone>();
    14                 Console.WriteLine(object.ReferenceEquals(phone3, phone4));//由于单例, 创建的是相同的对象
    15 
    16 
    17 
    18                 // 线程单例: 相同线程的实例相同, 不同线程的实例不同   应用场景:web请求 / 多线程操作
    19                 container.RegisterType<IPhone, AndroidPhone>(new PerThreadLifetimeManager());
    20                
    21                 IPhone iphone1 = null;
    22                 Action act1 = new Action(() =>
    23                 {   //使用单独的线程初始化IPhone1
    24                     iphone1 = container.Resolve<IPhone>();
    25                     Console.WriteLine($"iphone1由线程id={Thread.CurrentThread.ManagedThreadId}");
    26                 });
    27 
    28                 var result1 = act1.BeginInvoke(null, null);
    29 
    30                 IPhone iphone2 = null;
    31                 Action act2 = new Action(() =>
    32                 {
    33                     //单独线程初始化iphone2
    34                     iphone2 = container.Resolve<IPhone>();
    35                     Console.WriteLine($"iphone2由线程id={Thread.CurrentThread.ManagedThreadId}");
    36                 });
    37 
    38                 IPhone iphone3 = null;
    39                 var result2 = act2.BeginInvoke(t => //BeginInvoke是回调线程, 这将会和上面的Action act2的线程属于同一个线程, 注意BeginInvoke是当当前线程完成后, 再用当前的线程来执行现在的任务; 也就是act2完成会后, 再执行这里面的
    40                 {
    41                     iphone3 = container.Resolve<IPhone>();
    42                     Console.WriteLine($"iphone3由线程id={Thread.CurrentThread.ManagedThreadId}");
    43                     Console.WriteLine($"object.ReferenceEquals(iphone2, iphone3)={object.ReferenceEquals(iphone2, iphone3)}"); //结果为True
    44                 }, null);
    45 
    46                 act1.EndInvoke(result1); //调用EndInvoke获得执行解脱
    47                 act2.EndInvoke(result2);
    48 
    49                 Console.WriteLine($"object.ReferenceEquals(iphone1, iphone2)={object.ReferenceEquals(iphone1, iphone2)}");//结果为false
    50 
    51                 //了解知识:
    52                 //关于单例还可以使用分级容器来创建; 不同子容器创建的单例属于不同的实例, 相同子容器创建的单例对象属于同一个实例对象
    53                 container.RegisterType<IPhone, AndroidPhone>(new HierarchicalLifetimeManager());//分级容器单例
    54                 //获取子容器
    55                 IUnityContainer childContainer = container.CreateChildContainer();
    56 
    57                 //外部可释放单例 单例是全局唯一;一旦释放大家都没了;
    58                 container.RegisterType<IPhone, AndroidPhone>(new ExternallyControlledLifetimeManager());
    59 
    60                 //当真的发生了循环使用可以使用这个来创建 不推荐; 不要使用; 
    61                 container.RegisterType<IPhone, AndroidPhone>(new PerResolveLifetimeManager());
    View Code

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

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

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

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

     1  // ExeConfigurationFileMap  配置文件操作对象
     2                 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
     3                 //配置文件的路径到底应该放到哪个目录下: 配置文件最好是写到项目里, exe所在的目录, 而不是写到类库中; 注意始终复制
     4                 fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\Unity.Config");//找配置文件的路径
     5                 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
     6                 UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
     7 
     8                 IUnityContainer container = new UnityContainer();
     9                 section.Configure(container, "testContainer"); //testContainer  容器的名称
    10                 IPhone phone = container.Resolve<IPhone>();
    11                 phone.Call();
    12 
    13                 //使用别名构造
    14                 IPhone android = container.Resolve<IPhone>("Android");
    15                 android.Call();
    16 
    17                 //4.1 使用配置文件的时候, 不需要依赖细节类, 只需要引用接口的dll即可; 但是要注意的是, 还是必须要将细节类的   dll复制    到项目运行的根目录下的; 要不然就会报错
    View Code

    五.   AOP和IOC的组合使用:

      a)   配置文件代码:

     1 <!--Unity 5.8.6
     2 Unity.Interception   5.5.3
     3 
     4 配置文件中的dll名称改了
     5 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
     6 改为
     7 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
     8 
     9 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/>
    10 改为
    11 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
    12    -->
    13 
    14 <configuration>
    15   <!--配置文件的路径到底应该放到哪个目录下: 配置文件最好是写到项目里, 而不是写到类库中; 始终复制-->
    16   <!--当所有的引用都被移除之后, 注意一定要复制对应dll文件到当前程序运行目录下-->
    17   <configSections>
    18     <!--固定写法; -->
    19     <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
    20   </configSections>
    21   <unity>
    22     <!--AOP扩展-->
    23     <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
    24     <containers> <!--容器集合, 里面放着一个个container容器-->
    25       <!--这下面有一个个容器, 所有的容器名称不能重复--> 
    26 
    27         <!--name表示容器的名字, 前端使用的时候要传入-->  
    28       <container name="testContainerAOP"> 
    29         <extension type="Interception"/>
    30         <register type=" MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.AndroidPhone, MyIOCTest.Service.Extend">
    31           <interceptor type="InterfaceInterceptor"/> <!--加入AOP扩展, 访问AndroidPhone的时候, 执行AOP-->
    32           <!--权限认证-->
    33           <interceptionBehavior type=" MyIOCTest.Framework.AOP.AuthorizeBehavior, MyIOCTest.Framework"/>
    34           <!--发送短信-->
    35           <interceptionBehavior type=" MyIOCTest.Framework.AOP.SmsBehavior, MyIOCTest.Framework"/>
    36           <!--异常捕获-->
    37           <interceptionBehavior type=" MyIOCTest.Framework.AOP.ExceptionLoggingBehavior, MyIOCTest.Framework"/>
    38           <!--缓存-->
    39           <interceptionBehavior type=" MyIOCTest.Framework.AOP.CachingBehavior, MyIOCTest.Framework"/>
    40           <!--调用前写日志-->
    41           <interceptionBehavior type=" MyIOCTest.Framework.AOP.LogBeforeBehavior, MyIOCTest.Framework"/>
    42           <!--参数检查-->
    43           <interceptionBehavior type=" MyIOCTest.Framework.AOP.ParameterCheckBehavior, MyIOCTest.Framework"/>
    44           <!--调用后写日志-->
    45           <interceptionBehavior type=" MyIOCTest.Framework.AOP.LogAfterBehavior, MyIOCTest.Framework"/>
    46         </register>
    47         <register type=" MyIOCTest.Interface.IPhone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.AndroidPhone, MyIOCTest.Service.Extend" name="Android"/>
    48         <register type=" MyIOCTest.Interface.IMicrophone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Microphone, MyIOCTest.Service.Extend"/>
    49         <register type=" MyIOCTest.Interface.IHeadphone, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Headphone, MyIOCTest.Service.Extend"/>
    50         <register type=" MyIOCTest.Interface.IPower, MyIOCTest.Interface" mapTo=" MyIOCTest.Service.Power, MyIOCTest.Service.Extend"/>
    51         <register type=" MyIOCTest.IDAL.IBaseDAL, MyIOCTest.IDAL" mapTo=" MyIOCTest.DAL.BaseDAL, MyIOCTest.DAL">
    52         </register>
    53       </container> 
    54     </containers>
    55   </unity>
    56 </configuration>
    View Code

      b)  调用示例:

     1 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
     2                 fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\Unity.Config");//找配置文件的路径
     3                 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
     4                 UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
     5 
     6  IUnityContainer container = new UnityContainer();
     7  section.Configure(container, "testContainerAOP"); //AOP的容器扩展
     8  IPhone phone = container.Resolve<IPhone>();
     9  phone.Call();
    10 
    11  IPhone android = container.Resolve<IPhone>("Android");
    12  android.Call();
    View Code
  • 相关阅读:
    int k=0;k=k++;结果等于0,为什么?
    CentOS在无法连接外网的服务器上安装软件(以docker为例)
    docker搭建 elasticsearch 集群
    docker容器之间进行网络通信
    Kafka集群搭建
    Zookeeper集群搭建及常用命令
    SpringBoot将Swagger2文档导出为markdown或html
    Linux(CentOS7)虚拟机修改 NAT模式固定IP
    Linux Maven私服(Nexus)搭建
    Linux配置开机自启动
  • 原文地址:https://www.cnblogs.com/wxylog/p/10008819.html
Copyright © 2011-2022 走看看