zoukankan      html  css  js  c++  java
  • 几句代码简单实现IoC容器

    前言

      最近在调试EasyNetQ代码的时候发现里面有一段代码,就是IoC容器的简单实现,跟着他的代码敲了一遍,发现了奇妙之处。当然也是因为我才疏学浅导致孤陋寡闻了。他的思路就是通过动态调用构造函数生成对象,然后将对象保存,调用的时候进行单例调用,而且,代码中不会存在 new 字眼。所有实例对象的创建和映射都在容器中实现。当然,还是用第三方的容器比较稳妥,本文中只是很简单的一个示范。具体理解的是否正确,我也不敢说,只不过,能达到一些预期的效果,功能不够强大。

    解析

      首先,我们先添加几个接口。IServiceProvider,IServiceRegister,IServiceContainer (继承自前两个)

     /// <summary>
        /// 服务提供接口
        /// </summary>
        public interface IServiceProvider
        {
            /// <summary>
            /// 获取某个类型的服务实例
            /// </summary>
            /// <typeparam name="TService"> TService 接口 </typeparam>
            /// <returns>返回TService类型实例</returns>
            TService Resolve<TService>() where TService : class;
        }
     /// <summary>
        /// 服务注册接口
        /// </summary>
        public interface IServiceRegister
        {
            /// <summary>
            /// 注册一个服务工厂
            /// </summary>
            /// <typeparam name="TService">工厂类</typeparam>
            /// <param name="serviceCreator">创建一个新的接口实例</param>
            /// <returns>返回当前注册工厂,以便调用其他的register方法</returns>
            IServiceRegister Register<TService>(Func<IServiceProvider, TService> serviceCreator) where TService : class;
    
            /// <summary>
            /// 注册服务类型和服务实例
            /// </summary>
            /// <typeparam name="TService">接口实例</typeparam>
            /// <typeparam name="TImplementation">类型继承自TService</typeparam>
            /// <returns>返回当前注册工厂,以便调用其他的register方法</returns>
            IServiceRegister Register<TService, TImplementation>()
                where TService : class
                //TImplementation 继承自 TService
                where TImplementation : class, TService;
        }
     /// <summary>
        /// 对外接口Container,继承自IServiceProvider,IServiceRegister
        /// </summary>
        public interface IServiceContainer : IServiceProvider, IServiceRegister { }

      当然,最主要的还是 IServiceContainer 的实现,代码中有简单的注释,所以我就直接贴代码了。(看不懂的仔细认真一些就好)

      /// <summary>
        /// 服务提供类的实现,类似服务工厂
        /// </summary>
        public class ServiceProvider : IServiceContainer
        {
            /// <summary>
            ////// </summary>
            private readonly object syncLock = new object();
            /// <summary>
            /// 存储单例工厂
            /// </summary>
            private readonly ConcurrentDictionary<Type, object> factories = new ConcurrentDictionary<Type, object>();
            /// <summary>
            /// 存储注册类型
            /// </summary>
            private readonly ConcurrentDictionary<Type, Type> registrations = new ConcurrentDictionary<Type, Type>();
            /// <summary>
            /// 存储实例
            /// </summary>
            private readonly ConcurrentDictionary<Type, object> instances = new ConcurrentDictionary<Type, object>();
    
    
            #region 私有方法
            /// <summary>
            /// 判断服务是否已经被注册过
            /// </summary>
            /// <param name="serviceType"></param>
            /// <returns></returns>
            private bool ServiceIsRegistered(Type serviceType)
            {
                //判断是否在工厂或者注册库内已经注册过该类型
                return factories.ContainsKey(serviceType) || registrations.ContainsKey(serviceType);
            }
            #endregion
    
            /// <summary>
            /// 注册工厂
            /// </summary>
            /// <typeparam name="TService"></typeparam>
            /// <param name="serviceCreator"></param>
            /// <returns></returns>
            public IServiceRegister Register<TService>(Func<IServiceProvider, TService> serviceCreator) where TService : class
            {
                lock (syncLock) {
                    var serviceType = typeof(TService);
                    if (ServiceIsRegistered(serviceType)) { return this; }
    
                    //将服务添加到存储器中
                    factories.TryAdd(serviceType, serviceCreator);
                    return this;
                }
            }
    
            /// <summary>
            /// 注册实例类
            /// </summary>
            /// <typeparam name="TService">IService 必须实现一个构造器</typeparam>
            /// <typeparam name="TImplementation"></typeparam>
            /// <returns></returns>
            public IServiceRegister Register<TService, TImplementation>() where TService : class where TImplementation : class, TService
            {
                lock (syncLock)
                {
                    var serviceType = typeof(TService);
                    var implType = typeof(TImplementation);
    
                    if (ServiceIsRegistered(serviceType)) { return this; }
    
                    //如果注册的类不是继承自TService接口,抛出异常
                    if (!serviceType.IsAssignableFrom(implType))
                    {
                        throw new Exception(string.Format("类型 {0} 不继承接口 {1}", implType.Name, serviceType.Name));
                    }
                    //获取构造方法,必须只有一个构造方法(why?)
                    var constructors = implType.GetConstructors();
                    if (constructors.Length != 1)
                    {
                        throw new Exception(string.Format("服务实例必须有且只有一个构造方法.当前实例 {0} 拥有 {1} 个", implType.Name, constructors.Length.ToString()));
                    }
                    //添加
                    registrations.TryAdd(serviceType, implType);
                    return this;
                }
            }
    
            /// <summary>
            /// 返回一个实例
            /// </summary>
            /// <typeparam name="TService"></typeparam>
            /// <returns></returns>
            public virtual TService Resolve<TService>() where TService : class
            {
                var serviceType = typeof(TService);
                object service;
                //如果实例存储器中已经存在该实例,直接返回
                if (instances.TryGetValue(serviceType, out service))
                    return (TService)service;
                lock (syncLock)
                {
                    //加锁,再次判断
                    if (instances.TryGetValue(serviceType, out service))
                    { 
                        return (TService)service;
                    }
    
                    //如果注册器中存在该类型,创建该实例,并加入到实例存储器中
                    if (registrations.ContainsKey(serviceType))
                    {
                        var implementationType = registrations[serviceType];
                        service = CreateServiceInstance(implementationType);
                        instances.TryAdd(serviceType, service);
                    }
                    else if (factories.ContainsKey(serviceType))
                    {
                        service = ((Func<IServiceProvider, TService>)factories[serviceType])(this);
                        instances.TryAdd(serviceType, service);
                    }
                    else
                    {
                        throw new Exception(string.Format("服务类型 {0} 未注册", serviceType.Name));
                    }
                    return (TService)service;
                }
            }
    
            private object Resolve(Type serviceType)
            {
                return GetType()
                    .GetMethod("Resolve", new Type[0])
                    .MakeGenericMethod(serviceType)
                    .Invoke(this, new object[0]);
            }
    
            private object CreateServiceInstance(Type implementationType)
            {
                //获取构造器
                var constructors = implementationType.GetConstructors();
                //遍历构造器中的参数类型,获取参数
                var parameters = constructors[0]
                    .GetParameters()
                    .Select(parameterInfo => Resolve(parameterInfo.ParameterType))
                    .ToArray();
    
                //创建实例方法
                return constructors[0].Invoke(parameters);
            }
        }

    调用方式

    写个测试:IAnimal 接口中有Shout方法。在创建两个类继承自IAnimal,Dog,Cat。 IPeople中有个Pet方法,在创建两个类,Man,Women 继承自IPeople。 其中IPeople中初始化要传入IAnimal的实例,来确定养的宠物是猫还是狗.

    代码如下:

        public interface IAnimal
        {
            void Shout();
        }
    
        public class Dog : IAnimal
        {
            public void Shout()
            {
                Console.WriteLine("汪汪汪");
            }
        }
    
        public class Cat : IAnimal
        {
            public void Shout()
            {
                Console.WriteLine("喵喵喵");
            }
        }
        public interface IPeople
        {
            void Pet();
        }
    
        public class Man : IPeople
        {
            private IAnimal _animal;
    
            public Man(IAnimal animal)
            {
                _animal = animal;
            }
            public void Pet()
            {
                Console.WriteLine("[男]我有一个宠物,它在:");
                _animal.Shout();
            }
        }
    
        public class Woman : IPeople
        {
            private IAnimal _animal;
    
            public Woman(IAnimal animal)
            {
                _animal = animal;
            }
            public void Pet()
            {
                Console.WriteLine("[女]我有一个宠物,它在:");
                _animal.Shout();
            }
        }

    测试代码

      static void Main(string[] args)
            {
                Func<IServiceContainer> getContainer = () => new ServiceProvider();
                IServiceContainer container = getContainer();
    
                //注册
                container.Register(_ => container)
                    //注册Dog实例
                    .Register<IAnimal, Dog>()
                    //注册Woman实例
                    .Register<IPeople, Woman>();
    
                //得到people
                var people = container.Resolve<IPeople>();
                //调用pet方法
                people.Pet();
    
                Console.Read();
            }

    执行结果:

    同样,当我们把 Dog换成Cat

     我们把animal的注册去掉,由于people依赖于animal,所以会报错误。

     总结

      这是一个简单的IoC容器的实现,相信真正的要复杂的很多,不过看EasyNetQ就直接这么用的,我觉得如果需求不高的话,也可以用这种方法。而且,代码看起来清洁了许多,不用自己new对象,而且,内部依赖也可以轻松解决。我该去看看真正的容器是怎么实现的了,byebye

  • 相关阅读:
    软件测试从业者,试用期生存指南(完整版)
    学测试,看视频?NONONO,除非这种情况
    vi / vim 字符替换详解
    Centos 下 mysql 安装过程
    Web测试到底是在测什么(资料合集)
    我花了两个小时,写了这份年终总结 。
    拒绝无效加班 !
    你离月薪30K还差哪些?
    从业十年分享:你应该知道的一些测试职业事实!
    别再TM跟我说找不到满意的工作!
  • 原文地址:https://www.cnblogs.com/panzi/p/6274193.html
Copyright © 2011-2022 走看看