幾句程式碼簡單實現IoC容器

丶Pz發表於2017-01-11

前言

  最近在除錯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

 

相關文章