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