• <二>IOC&DI 简单实现


    一、IOC 控制反转

    面向对象编程的一种设计原则,可以用来降低代码之间的耦合度。

    IOC容器:就是专门做实例化的工厂。

     二、依赖倒置

    一种设计模式,以前是高层依赖低层,现在不这么干了,所有层级都依赖于抽象,并且负责实现自己继承的抽象。

    三、实现

    IOC实现的三步:1、注册Register,先告诉IOC抽象与实现的关系。2、有了关系才有了Resolve生成实例。3、有了实例就能依赖IOC容器注入实例。

    1、实现register和Resolve

     在上一节的common中添加两个类,叫TestContainer和接口ITestContainer。

     public interface ITestContainer
        {
            /// <summary>
            /// 注册抽象与实例的映射关系
            /// </summary>
            /// <typeparam name="IT"></typeparam>
            /// <typeparam name="T"></typeparam>
            void Register<IT, T>() where T : IT;
            /// <summary>
            /// 通过映射返回实例
            /// </summary>
            /// <typeparam name="IT"></typeparam>
            /// <returns></returns>
            IT Resolve<IT>();
        }
     public class TestContainer:ITestContainer
        {
            /// <summary>
            /// 映射字典
            /// </summary>
            private Dictionary<string, Type> RegisterTypes = new Dictionary<string, Type>();
            /// <summary>
            /// 注册
            /// </summary>
            /// <typeparam name="IT">抽象</typeparam>
            /// <typeparam name="T">实现抽象细节</typeparam>
            public void Register<IT, T>() where T : IT
            {
                string key = typeof(IT).FullName;
                if (!RegisterTypes.ContainsKey(key))
                {
                    RegisterTypes.Add(key, typeof(T));
                }
            }
            /// <summary>
            /// 返回实例
            /// </summary>
            /// <typeparam name="IT"></typeparam>
            /// <returns></returns>
            /// <exception cref="Exception"></exception>
            public IT Resolve<IT>()
            {
                string key = typeof(IT).FullName;
                if (!RegisterTypes.ContainsKey(key))
                {
                    throw new Exception($"未注册{key},不能被实例化!");
                }
                Type instanceType = RegisterTypes[key];
                Object instance = Activator.CreateInstance(instanceType);
                return (IT)instance;
            }
        }

    如上代码,Register方法将注册进来的抽象名和实例的映射保存到一个字典里,Resolve方法,通过传进来的抽象去注册映射字典里找到对应的实现细节类,并返回该实现类的实例。

    ITestContainer container=new TestContainer();
    container.Register<IUserDal,UserMySqlDal>();
    IUserDal userDal = container.Resolve<IUserDal>();
    IUserBll userBll = Factory.CreateBll(userDal);
    bool result= userBll.Login(1);
    Console.WriteLine(result);

    如上,修改一下UI层的代码,先调用Register注册userDal的关联,然后再用Resolve实例化方法返回实例。可以实现。

     2、实现构造函数注入实例(依赖注入)

    上面我们只是实现了简单的一个注册和实例化方法。只能对UserDal这种只有无参构造方法的类进行实例化。遇到像UserBll这个需要传一个IUserDal参数的构造方法,那就会报错了。

    那么如果要实现这种构造函数带一个参数的类呢?只有先实例了UserDal再去实例UserBll。修改一下revolse的方法。

     public IT Resolve<IT>()
            {
                string key = typeof(IT).FullName;
                if (!RegisterTypes.ContainsKey(key))
                {
                    throw new Exception($"未注册{key},不能被实例化!");
                }
                Type instanceType = RegisterTypes[key];
                ConstructorInfo userConstructor = null;     //使用的构造方法
                ConstructorInfo[] constructors=instanceType.GetConstructors();//获取类所有的构造方法
                //获取构造方法
                if (constructors != null && constructors.Length > 0)
                {
                    foreach (var constructor in constructors)
                    {
                        userConstructor = constructor; //暂时取第一个
                        break;
                    }
                }
                //保存参数的实例
                List<object> constructorParamInfos = new List<object>();
                //获取构造方法的参数
                if (userConstructor != null)
                {
                    ParameterInfo[] paramInfos= userConstructor.GetParameters();
                    if (paramInfos != null && paramInfos.Length > 0)
                    {
                        foreach (ParameterInfo pInfo in paramInfos)
                        {
                            //对参数进行实例化
                            Type pType= pInfo.ParameterType;
                            string pTypeKey = pType.FullName;
                            if (RegisterTypes.ContainsKey(pTypeKey))
                            {
                                Type pInstanceType = RegisterTypes[pTypeKey];
                                Object pInstance = Activator.CreateInstance(pInstanceType);
                                constructorParamInfos.Add(pInstance);
                            }
                            else
                            {
                                throw new Exception($"未注册{pTypeKey},不能被实例化!");
                            }
                        }
                    }
                }
                Object instance = null;
                if(constructorParamInfos.Count()>0)
                {
                    instance = Activator.CreateInstance(instanceType,constructorParamInfos.ToArray());
                }
                else
                {
                    instance = Activator.CreateInstance(instanceType);
                }
                return (IT)instance;
            }

    如上述代码,先通过反射获取实例类型的所有构造方法,暂时先取第一个构造方法,获取构造方法中的所有参数,遍历参数并实现参数的实例化加到参数实例集合里。这叫什么?这个实现就叫做依赖注入。即在构造B对象的过程中发现B依赖于A对象,那么在构造B对象的时候能够动态的构造A对象并传递给B,让B能够正常的通过构造方法进行实例化。

    3、依赖注入的好处

    下面对比一下用factory实例UserBll和使用依赖注入实例UserBll。依赖注入屏蔽掉了实例UserDAL的细节。而一般工厂必须要接收UserDal的实例才能去实例UserBLL。那如果UserBll的构造方法依赖100个其他实例,那么还需要先实例化100个实例再传给UserBll构造方法。这代码写下来,手酸不说,这样的代码好吗?所以依赖注入的好处便是上端不再需要去实现实例UserBll的所有细节,就是不管你UserBll的实例化需要依赖多少其他实例,我只需要写一条语句就行。

    ITestContainer container = new TestContainer();
    container.Register<IUserDal, UserMySqlDal>();
    container.Register<IUserBll, UserBll>();
    IUserBll userBll = container.Resolve<IUserBll>();   //依赖注入实现实例
    //IUserDal userDal = container.Resolve<IUserDal>(); //
    //IUserBll userBll = Factory.CreateBll(userDal);    //工厂生成实例
    bool result = userBll.Login(1);
    Console.WriteLine(result);

    4、递归实现多层依赖注入

    上面我们实现了一层的构造方法调用,那如果UserDal的构造方法需要传入一个另一个对象呢?即UserBll需要先构造UserDal,要构造UserDal需要先构造UserContext。这个时候怎么实现呢?这种结构是不是树结构?那么遍历树的算法有很多种(有兴趣可以去研究下数据结构与算法)。递归应该是最简单的了,那么就用递归来实现吧。递归存在内存泄露的风险,实际项目中最好避免使用。修改代码

    加一个IUserContext,和UserContext,再给UserMysqlDal加上构造函数。

     public class UserMySqlDal:IUserDal
        {
            private IUserContext UserContext { get; set; }
            public UserMySqlDal(IUserContext userContext)
            {
                UserContext = userContext;
            }
            public object Find(int id)
            {
                return null;
            }
        }
     public IT Resolve<IT>()
            {
                Type type = typeof(IT);
                object instance = ResolveObject(type);
                return (IT)instance;
            }
            /// <summary>
            /// 递归实例化对象
            /// </summary>
            /// <param name="objectType"></param>
            /// <returns></returns>
            /// <exception cref="Exception"></exception>
            private object ResolveObject(Type objectType)
            {    
                string key = objectType.FullName;
                if (!RegisterTypes.ContainsKey(key))
                {
                    throw new Exception($"未注册{key},不能被实例化!");
                }
                Type instanceType = RegisterTypes[key];
                ConstructorInfo userConstructor = null;     //使用的构造方法
                ConstructorInfo[] constructors=instanceType.GetConstructors();//获取类所有的构造方法
                //获取构造方法
                if (constructors != null && constructors.Length > 0)
                {
                    foreach (var constructor in constructors)
                    {
                        userConstructor = constructor; //暂时取第一个
                        break;
                    }
                }
                //保存参数的实例
                List<object> constructorParamInfos = new List<object>();
                //获取构造方法的参数
                if (userConstructor != null)
                {
                    ParameterInfo[] paramInfos= userConstructor.GetParameters();
                    if (paramInfos != null && paramInfos.Length > 0)
                    {
                        foreach (ParameterInfo pInfo in paramInfos)
                        {
                            //对参数进行实例化
                            Type pType= pInfo.ParameterType;
                            Object pInstance = ResolveObject(pType);
                            constructorParamInfos.Add(pInstance);
                            //string pTypeKey = pType.FullName;
                            //if (RegisterTypes.ContainsKey(pTypeKey))
                            //{
                            //    Type pInstanceType = RegisterTypes[pTypeKey];
                            //    Object pInstance = Activator.CreateInstance(pInstanceType);
                            //    constructorParamInfos.Add(pInstance);
                            //}
                            //else
                            //{
                            //    throw new Exception($"未注册{pTypeKey},不能被实例化!");
                            //}
                        }
                    }
                }
                Object instance = null;
                if(constructorParamInfos.Count()>0)
                {
                    instance = Activator.CreateInstance(instanceType,constructorParamInfos.ToArray());
                }
                else
                {
                    instance = Activator.CreateInstance(instanceType);
                }
                return instance;
            }

    5、处理构造方法优先选择

    上面的代码选择构造方法构造的时候暂时选取的是第一个。这个显然是不合理的。Unity和autofac采用的是参数最多的构造方法,而.netcore内置的IOC采用的时候参数的类型最多的构造方法。这两种默认算法这里就不写了,还有另一种叫指定方式。指定方式怎么指定呢?可以利用特性来指定。

     public class DefaultInstantiateAttribute: Attribute
        {
        }
      //获取构造方法
                if (constructors != null && constructors.Length > 0)
                {
                    //获取第一个特性指定的构造方法
                    var defaultConstructor = constructors.Where(c => c.IsDefined(typeof(DefaultMemberAttribute), true)).ToList(); ;
                    if (defaultConstructor != null&& defaultConstructor.Count()>0)
                    {
                        userConstructor = defaultConstructor[0];
                    }
                    else
                    {
                        foreach (var constructor in constructors)
                        {
                            ///实现构造方法的优先选择
                            userConstructor = constructor; //暂时取第一个
                            break;
                        }
                    }
                }

    四、总结

    IOC(控制反转):是一种设计模式。

    DI(依赖注入):是一种实现方式。

  • 相关阅读:
    数组和排序算法(冒泡、选择、插入排序)
    异常
    线程的五个状态,sleep和wait
    ArrayList、Vector、LinkedList
    String,StringBuffer,StringBuilder的区别
    Math.round(),Math.ceil(),Math.floor()的区别
    单例模式之双重锁模式、静态内部类模式、饿汉模式、懒汉模式,和安全的懒汉模式
    工厂模式简单的汽车工厂
    存储过程的优点
    数据库SQL特点数据查询,数据操纵,数据定义,数据控制,建立索引, 事务acid,数据库隔离级别
  • 原文地址:https://www.cnblogs.com/choii/p/16387540.html
Copyright © 2020-2023  润新知