<二>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(依赖注入):是一种实现方式。

 

posted @ 2022-06-18 17:21  许轩霖  阅读(50)  评论(0编辑  收藏  举报