• IOC


    前言

    文中内容是结合多篇文章的学习笔记,记录下来以免时间久了忘记。

    面向对象设计(OOD)有助于我们开发出高性能、易扩展以及易复用的程序。其中,OOD有一个重要的思想那就是依赖倒置原则(DIP),并由此引申出IOC、DI以及IOC容器等概念。通过本文我们将一起学习这些概念,并理清他们之间微妙的关系。

    概念

    依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念),依赖抽象而不是依赖细节。

    控制反转(IOC):一种反转流、依赖和接口的方式(DIP的具体实现方式),通过抽象获取细节,实现这个动作的时候就是控制反转。

    依赖注入(DI):IOC的一种实现方式,用来反转依赖(IOC的具体实现方式),DI是实现IOC的一种技术手段。

    IOC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架),IOC容器实际上是一个DI框架,它能简化我们的工作量。它包含以下几个功能:

    • 动态创建、注入依赖对象。
    • 管理对象生命周期。
    • 映射依赖关系。

    图示

    img

    依赖

    以三层架构为例,从上到下,UI->BLL->DAL这就是依赖,这种最基本的依赖是依赖于细节的,耦合度较高,DAL如果修改了代码,则底层到高层的代码都需要进行改变。所以需要进行解耦,由此引出依赖倒置原则(DIP)。

    依赖倒置DIP

    耦合关系就是依赖关系,如果依赖关系相当繁杂,牵一发而动全身,很难维护;依赖关系越少,耦合关系就越低,系统就越稳定,所以我们要减少依赖。

    幸亏Robert Martin大师提出了面向对象设计原则----依赖倒置原则:   

    • A. 高层模块不应依赖于低层模块,两者应该依赖于抽象。  
    • B. 抽象不应该依赖于实现,实现应该依赖于抽象。

    理解:A.上层是使用者,下层是被使用者,这就导致的结果是上层依赖下层了,下层变动了,自然就会影响到上层了,导致系统不稳定,甚至是牵一发而动全身。那怎么减少依赖呢?就是上层和下层都去依赖另一个抽象,这个抽象比较稳定,整个就来说就比较稳定了。

    B.面向对象编程时面向抽象或者面向借口编程,抽象一般比较稳定,实现抽象的具体肯定是要依赖抽象的,抽象不应该去依赖别的具体,应该依赖抽象。

    依赖倒置原则,它转换了依赖,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。通俗的讲,就是高层模块定义接口,低层模块负责实现。

    控制反转IOC

    DIP是一种 软件设计原则,它仅仅告诉你两个模块之间应该如何依赖,但是它并没有告诉如何做。IOC则是一种 软件设计模式,它告诉你应该如何做,来解除相互依赖模块的耦合。控制反转(IOC),它为相互依赖的组件提供抽象,将依赖(低层模块)对象的获得交给第三方(系统)来控制即依赖对象不在被依赖模块的类中直接通过new来获取

    控制反转IOC是Inversion of Control的缩写,是说对象的控制权进行转移,转移到第三方,比如转移交给了IOC容器,它就是一个创建工厂,你要什么对象,它就给你什么对象,有了IOC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IOC容器了,通过IOC容器来建立它们之间的关系。

    软件设计原则:原则为我们提供指南,它告诉我们什么是对的,什么是错的。它不会告诉我们如何解决问题。它仅仅给出一些准则,以便我们可以设计好的软件,避免不良的设计。一些常见的原则,比如DRY、OCP、DIP等。

    软件设计模式:模式是在软件开发过程中总结得出的一些可重用的解决方案,它能解决一些实际的问题。一些常见的模式,比如工厂模式、单例模式等等。

    依赖注入(DI)

    上面说到控制反转,是一个思想概念,但是也要具体实现的,上面的配置文件也是一种实现方式。依赖注入提出了具体的思想。

    依赖注入DI是Dependency Injection缩写,它提出了“哪些东东的控制权被反转了,被转移了?”,它也给出了答案:“依赖对象的创建获得被反转”。

    所谓依赖注入,就是由IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。

    依赖注入(DI),它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象。

    IOC容器

    IOC中最基本的技术就是“反射(Reflection)”编程,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。反射的应用是很广泛的,很多的成熟的框架,比如象Java中的hibernate、Spring框架,.Net中 NHibernate、Spring.NET框架都是把“反射”做为最基本的技术手段。

    我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

    动手实现一个无限层级的IOC容器

    如果觉得代码太多不易阅读,可以直接阅读核心代码,只有简单的三步结合对应的概念和最开始的图示,可以很快在脑海中理清基本思路。

    IFishServiceCollection

    public interface IFishServiceCollection
    {
        /// <summary>
        /// 添加具有实现的在serviceType中指定的类型的临时服务
        /// 在ImplementationType中指定的类型为指定的Microsoft.Extensions.DependencyInjection.IServiceCollection。
        /// </summary>
        /// <param name="serviceType">要注册的服务的类型</param>
        /// <param name="implementationType">服务的实现类型</param>
        /// <param name="shortName">服务的实现类型别名</param>
        void AddTransient(Type serviceType,Type implementationType,string shortName =null);
        /// <summary>
        /// 获取指定类型的服务对象
        /// </summary>
        /// <typeparam name="T">要获取的服务类型</typeparam>
        /// <returns>生产的服务</returns>
        T GetService<T>(string shortName=null);
    }
    

    FishServiceCollection

    public class FishServiceCollection : IFishServiceCollection
        {
    
            private Dictionary<string, Type> richarDictionary = new Dictionary<string, Type>();
    
            //拼接要注册的服务的类型和别名
            private string GetKey(Type type, string shortName) => $"{type.FullName}___{shortName}";
            //{
            //    return $"{type.FullName}___{shortName}";
            //}
    
            /// <summary>
            /// 注册服务--其实就是把抽象和具体的映射关系保存起来
            /// </summary>
            /// <param name="serviceType">要注册的服务的类型(抽象:接口等)</param>
            /// <param name="implementationType">服务的实现类型(细节:实例)</param>
            /// <param name="shortName">别名</param>
            public void AddTransient(Type serviceType, Type implementationType,string shortName)
            {
                //使用别名完成多实现功能
                string Key = GetKey(serviceType,shortName);
                richarDictionary.Add(Key, implementationType);
                //01 将对象与细节储存在字典内
                //richarDictionary.Add(serviceType.FullName,implementationType);
            }
            /// <summary>
            /// 获取服务实例
            /// </summary>
            /// <typeparam name="T">要获取的服务类型(抽象:接口)</typeparam>
            /// <returns>生产的服务</returns>
            public T GetService<T>(string shortName)
            {
                #region 第一版 简单层级
                ////02 通过字典取出具体的类型
                //Type type = richarDictionary[typeof(T).FullName];
                ////03 通过反射创建实例
                ////return (T)Activator.CreateInstance(type);//如果需要构造的对象中构造函数参数 不为空;就报错了 调用的无参数的构造函数
    
                ////1.先取构造 当前类构造函数中需要的参数的对象   确定构造当前对象使用那个构造函数(默认选择参数最多的构造函数)
                //var contrArray = type.GetConstructors();//获取Type的所有构造函数
                ////2.根据构造函数参数数量降序配列,再取第一个==获取到构造函数参数最多的这一个
                //var ctor = contrArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
                ////3.准备构造函数的参数
                //List<object> prarlist = new List<object>();
                //foreach (ParameterInfo para in ctor.GetParameters())
                //{
                //    Type paraType = para.ParameterType;
                //    Type parTargetType = richarDictionary[paraType.FullName];
                //    #region 思路引导
                //    //1* 确定使用那个构造函数
                //    //var contrArray1 = parTargetType.GetConstructors();
                //    //2* 根据构造函数参数数量降序排序,再取第一个==获取到构造函数参数最多的这一个
                //    //var ctor1 = contrArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
                //    //3* 准备构造函数的参数········
                //    //不知道要构造的对象的构造函数具体依赖多少个层级;
                //    //永远写不完
                //    //怎么办?使用递归
                //    #endregion
                //    var target = Activator.CreateInstance(parTargetType);
                //    prarlist.Add(target);
                //}
                ////4.带参数的构造当前对象
                //return (T)Activator.CreateInstance(type, prarlist.ToArray());
                #endregion
                #region 多层级
                Type type = richarDictionary[GetKey(typeof(T), shortName)];
                return (T)this.GetService(type);
                #endregion
            }
    
            private object GetService(Type type)
            {
                #region 构造函数注入
                //1.先去构造 当前类构造函数中需要的参数的对象   确定构造当前对象使用那个构造函数(默认选择参数最多的构造函数)
                var contrArray = type.GetConstructors();//获取Type的所有构造函数
                //2.根据构造函数参数数量降序配列,再取第一个==获取到构造函数参数最多的这一个
                //2.1 这是默认情况下,选择构造函数参数最多的,但是有特殊情况,如果就需要选择一个自己指定的构造函数呢?
                //2.2 需求:需要通过自己指定选择不同的构造函数,如果没有指定,就选择默认
                //2.3 如何解决呢?=>通过特性解决
                //2.4 通过特性支持,可以在构造函数上,标记一个特性,在选择构造函数的时候,就判断,如果标记有特性,就选择标记有特性的这个构造函数,如果没有特性,就选择默认最多的构造函数
                //2.4.1 找标记的有特性的构造函数
                ConstructorInfo ctor = contrArray.Where(a => a.IsDefined(typeof(SelectConstructorAttribute))).FirstOrDefault();
                if (ctor == null)
                {
                    ctor = contrArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
                }
    
                //3.准备构造函数的参数
                List<object> prarlist = new List<object>();
                foreach (ParameterInfo para in ctor.GetParameters())
                {
                    Type paraType = para.ParameterType;
    
                    string shortName = GetKey(paraType,null);
    
                    Type parTargetType = richarDictionary[shortName];
                    //var target = Activator.CreateInstance(parTargetType);
    
                    var target = this.GetService(parTargetType);//递归
                    prarlist.Add(target);
                }
                #endregion
                #region 对象注入
                //构造对象
                object oInstance = Activator.CreateInstance(type, prarlist.ToArray());
                #endregion
                #region 属性注入
                //找出对象中需要做注入的属性,然后实例化属性对应类型的对象,赋值给属性
                //1.找出所有的属性;
                //2.找出指定标识,按照有标识的去给属性做注入 标识--Attribute
    
                //拿出所有属性,使用特性判断需要注入的属性
                var attArray = type.GetProperties().Where(p=>p.IsDefined(typeof(PropertyInjeictionAttribute),true));
                foreach (var prop in attArray)
                {
                    //找到具体类型
                    Type propType = prop.PropertyType;
                    string shortName = GetKey(propType,null);
                    Type propImType = richarDictionary[shortName];
                    //构造
                    object propInstance = GetService(propImType);
                    prop.SetValue(oInstance,propInstance);
                }
                #endregion
                #region 方法注入
                foreach (var method in type.GetMethods().Where(p => p.IsDefined(typeof(MethodImplAttributes), true)))
                {
                    List<object> paraInjectionList = new List<object>();
                    foreach (var para in method.GetParameters())
                    {
                        Type paraType = para.ParameterType;
                        string shortName = GetKey(paraType, null);
                        Type paraImType = richarDictionary[shortName];
                        object mInstance = GetService(paraImType);
                        paraInjectionList.Add(mInstance);
                    }
                    method.Invoke(oInstance,paraInjectionList.ToArray());
                }
                #endregion
                //多实现注入--一个接口多实现后,希望在都注册以后,能够选择性的获取具体的实例
                //1.当然要标识一些,一个接口的两个实现在注册时候必然要标识下
                //2.在获取的时候,就可以通过这个标识来构造对应的对象
                //3.起个别名
                return oInstance;
            }
        }
    

    MethodInjeictionAttribute

    [AttributeUsage(AttributeTargets.Method)]
    public class MethodInjeictionAttribute:Attribute
    {
    }
    

    PropertyInjeictionAttribute

    [AttributeUsage(AttributeTargets.Property)]
    public class PropertyInjeictionAttribute:Attribute
    {
    }
    

    SelectConstructorAttribute

    [AttributeUsage(AttributeTargets.Constructor)]
    public class SelectConstructorAttribute:Attribute
    {
    }
    

    核心代码

    // 01 创建字典		就是用来保存实现和抽象的映射关系,就相当于配置文件里的相关信息
    private Dictionary<string, Type> richarDictionary = new Dictionary<string, Type>();
    // 02 将对象与细节储存在字典内	注册服务--其实就是把抽象和具体的映射关系保存起来
    public void AddTransient(Type serviceType, Type implementationType,string shortName)
    {
        //使用别名完成多实现功能
        string Key = GetKey(serviceType,shortName);
        richarDictionary.Add(Key, implementationType);
        //将对象与细节储存在字典内
        //richarDictionary.Add(serviceType.FullName,implementationType);
    }
    //03 通过字典取出具体类型,使用反射创建实例
    public T GetService<T>(string shortName)
    {
        //通过字典取出具体的类型
    	Type type = richarDictionary[typeof(T).FullName];
    	//通过反射创建实例
    	return (T)Activator.CreateInstance(type);
    }
    //04 使用递归完成多层级
    Type type = richarDictionary[GetKey(typeof(T), shortName)];
    return (T)this.GetService(type);
    

    相关文章

    IoC模式(依赖、依赖倒置、依赖注入、控制反转)

    深入理解DIP、IoC、DI以及IoC容器

    登峰造极的成就源于自律
  • 相关阅读:
    2073: [POI2004]PRZ
    BZOJ 3669: [Noi2014]魔法森林
    Dominator Tree & Lengauer-Tarjan Algorithm
    BZOJ 3526: [Poi2014]Card
    BZOJ 2733: [HNOI2012]永无乡
    BZOJ 2929: [Poi1999]洞穴攀行
    BZOJ 3730: 震波
    BZOJ 1778: [Usaco2010 Hol]Dotp 驱逐猪猡
    BZOJ 1195: [HNOI2006]最短母串
    BZOJ 4030: [HEOI2015]小L的白日梦
  • 原文地址:https://www.cnblogs.com/fishpond816/p/13797940.html
Copyright © 2020-2023  润新知