白話系列之IOC,三個類實現簡單的Ioc


前言:博客園上已經有很多IOC的博客.而且很多寫的很好,達到開源的水平,但是對於很多新人來說,只了解ioc的概念,以及怎么去使用ioc.然后想更進一步去看源碼,但是大部分源碼都比較困難,當不知道一個框架整體時候,從每一個片段去推理,其實很耗費時間,所以這篇博客,從autofac及.netcore自帶的ioc的源碼中抽象出最核心代碼,先了解整個ioc的實現方式,其他的所有好的ioc,只是在這個框架上面進行縫縫補補.

友情提示下,這個ioc雖然能夠使用,但是只是為了做例子,所以只保留最核心代碼,要使用還是使用autofac或成熟的ioc框架.

一:老生常談

問:什么是ioc?

答:依賴注入,是一種思想,由於過分模糊,所以提出DI的觀點:被注入對象依賴IOC容器配置依賴對象

問:有什么用?

答:解決高耦合問題,以前沒有ioc的時候,每次都執行的是new操作,這沒什么不好,但是假設,本來使用sqlserver,通過IConnection  conn = new Sqlserver();方式初始化所有的連接操作,但是現在老板要求改成mysql當做數據庫,如果按照new的方式,得一個個去改,全局搜索,全局替換,其實也是可以的,無非是人累點,還需要一遍遍去檢查,看哪里漏了,這時候就懷念Ioc的好處了,只需在容器內改變一處,便全局改變.當然,這里並不是少寫了幾行new代碼,代碼還是一樣的多,只不過new的操作讓容器去處理了.擬人化的方式就是,new的方式就相當於以前沒群的時候,你本來是密令是10, 你一個個去通知你所想要通知的人即new,但是現在呢,密令被敵人偷聽去了,你需要更改,這次改成20,你就得一個個通知,但是現在你每次聯系別人都是通過手機去聯系,你不需要管手機是怎么發送給對方的,只需要知道你給手機一個通知,其他人都可以立馬收到,那么手機在這里扮演的就是容器的概念,一次更改,全部獲悉

二:理論結束,開始思考准備ioc之前需要准備的東西

1.首先建立一個收集器,收集可能需要new的對象,那么會有幾種生命周期去new一個對象?

常用的就是單例模式(singleton), 每次直接new對象,即用即拋(Transient),還有當前請求的主線程中只會創建一個對象(Scope,注意,單例是所有請求都會公用一個對象),所以,先定義接口,如下,命名即功能

    public interface IServiceCollection { IServiceCollection AddTransient<T1, T2>() where T2 : T1; IServiceCollection AddTransient<T1>(T1 t2); IServiceCollection AddSingleton<T1, T2>() where T2 : T1; IServiceCollection AddSingleton<T1>(T1 t2); IServiceCollection AddScoped<T1, T2>() where T2 : T1; IServiceCollection AddScoped<T1>(T1 t2); IServiceProvider BuildServiceProvider(); }

 2.其次,建立一個對象提供器,獲取容器內的可以獲取的對象

    越簡單越好,直接通過類型獲取對應的對象,同樣,接口定義如下:

    public interface IServiceProvider { T GetRequiredService<T>(); Object GetRequiredService(Type type); }

3.Collection對收集的對象進行保存,並且需要對每個對象進行區分是Singleton,scoped,還是transient的

注意:我覺得在設計一個好的代碼時候,得弄清楚當前類型具體的作用,然后如果作用不一樣,那么得重新創建一個類型,當然如果后期發現沒必要,可以合並,但是前期還是得分清楚點,就如sql中的范式及反范式.

 3.1:首先定義枚舉,區分當前的類型需要new的類型,與上文中的一致

 

    public enum ServiceLifetime { Singleton = 0, Transient = 1, Scoped = 2 }

 

  3.2:其次需要保存注入進去的類型及周期,因為不去考慮架構,只考慮那ioc的意思,就盡量簡化代碼

 

三:直接開始擼代碼

 

1.通過Type創建對象,先默認只創建當前無參構造器,代碼很簡單

 

        public Object GetCache(IDictionary<Type, IServiceCache> typePairs)
        {
            if (_obj == null)
            {
                _obj = Activator.CreateInstance(_type);
            }
            switch (_typeEnum)
            {
                case ServiceLifetime.Transient:
                    return Activator.CreateInstance(_type);
                case ServiceLifetime.Singleton:
                    return _obj;
                case ServiceLifetime.Scoped:
                    throw new Exception("目前不支持scoped");
                default:
                    throw new Exception("請傳遞正確生命周期");
            }
        }

 

 

 

DeepClone的寫法就是通過序列化的方式實現的,JsonConvert

      public static Object DeepClone(this Object obj, Type type)
        {
            return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), type);
        }

2.collection保存對應的對象,繼承IServiceCollection接口

 

    public class ServiceCollection : IServiceCollection
    {
        private ConcurrentDictionary<Type, IServiceCache> _typePairs;
        public ServiceCollection()
        {
            _typePairs = new ConcurrentDictionary<Type, IServiceCache>();
        }
        public IServiceCollection AddScoped<T1, T2>() where T2 : T1{}
        public IServiceCollection AddScoped<T1>(T1 t2){}
        public IServiceCollection AddSingleton<T1, T2>() where T2 : T1{}
        public IServiceCollection AddTransient<T1, T2>() where T2 : T1{}
        public IServiceCollection AddSingleton<T>(T t2){}
        public IServiceProvider BuildServiceProvider(){}
    }

 實現Singleton及Transient,此處Scoped有些額外的語法糖,等后期會猜想實現

       public IServiceCollection AddSingleton<T1, T2>() where T2 : T1
        {
            Type t1 = typeof(T1);
            Type t2 = typeof(T2);
            ServiceTypeCache service = new ServiceTypeCache(t2, ServiceLifetime.Singleton);
            if (!_typePairs.TryAdd(t1, service))
            {
                throw new Exception("在注入對象時,有相同對象存在");
            }
            return this;
        }
        public IServiceCollection AddTransient<T1, T2>() where T2 : T1
        {
            Type t1 = typeof(T1);
            Type t2 = typeof(T2);
            ServiceTypeCache service = new ServiceTypeCache(t2, ServiceLifetime.Transient);
            if (!_typePairs.TryAdd(t1, service))
            {
                throw new Exception("在注入對象時,有相同對象存在");
            }
            return this;
        }

 3:實現IServiceProvider接口,就是從Cache中獲取對應的對象

    public class ServiceProvider : IServiceProvider { private IDictionary<Type, IServiceCache> _cache; public ServiceProvider(IDictionary<Type, IServiceCache> valuePairs) { _cache = valuePairs; } public T GetRequiredService<T>() { Type t = typeof(T); return (T)GetRequiredService(t); } public object GetRequiredService(Type type) { IServiceCache service = null; if (!_cache.TryGetValue(type, out service)) { throw new Exception("獲取參數對象沒有注入"); } return service.GetCache(); } }

 

 4:將Collection轉變為ServiceProvider

        public IServiceProvider BuildServiceProvider()
        {
            return new ServiceProvider(_typePairs);
        }

5:OK,現在來試試這種簡單注入

    public interface ITestTransient
    {
        void Write();
    }
    public class TestATransient : ITestTransient
    {
        public void Write()
        {
            Console.WriteLine("----------------A----------------");
        }
    }
    public class TestBTransient : ITestTransient
    {
        public void Write()
        {
            Console.WriteLine("----------------B----------------");
        }
    }

 

class Program { static void Main(string[] args) { InitA(); InitB(); Console.Read(); } public static void InitA() { IServiceCollection collection = new ServiceCollection(); collection.AddTransient<ITestTransient, TestATransient>(); IServiceProvider provider = collection.BuildServiceProvider(); provider.GetRequiredService<ITestTransient>().Write(); } public static void InitB() { IServiceCollection collection = new ServiceCollection(); collection.AddTransient<ITestTransient, TestBTransient>(); IServiceProvider provider = collection.BuildServiceProvider(); provider.GetRequiredService<ITestTransient>().Write(); } }

 

測試OK,只要在后面的代碼中使用同一個provider,那么從IOC容器中獲取的實例都是相同,改一處便全部都能修改

6.延伸,現在通過構造器注入其他代碼,比如 

class A{}
class B
{
   public B(A a) { }
}

猜想下,遇到這種構造器注入時候,怎么去處理,其實和創建Type對象一直,通過CreateInstance(Type, Object[] param);去創建,param是每個需要注入的類型對象

OK,那我們來改下代碼,將獲取Object對象的方法添加參數,因為構造器里面注入的參數都是從IOC里面獲取

    public interface IServiceCache { Object GetCache(IDictionary<Type, IServiceCache> typePairs); }

獲取當前Type類型的構造器,默認獲取參數最多的,參數一樣多的,獲取最后一個,注:這里可以添加一個特性,標明優先構造這個構造器,自己添加就好,寫法盡量簡單

        private List<Type> GetConstructor() { ConstructorInfo[] a = _type.GetConstructors(); ConstructorInfo b = null; Int32 length = 0; foreach (ConstructorInfo info in a) { if (info.GetParameters().Length >= length) { b = info; } } ParameterInfo[] pa = b.GetParameters(); List<Type> list = new List<Type>(); foreach (var p in pa) { list.Add(p.ParameterType); } return list; }

構造器參數,就需要從typePairs里面獲取,注意,這里的所有參數都必須從IOC容器中獲取,當然這里會有一個問題就是相互引用,這時候就需要注意下

        public Object GetCache(IDictionary<Type, IServiceCache> typePairs) { if (_obj == null) {
          //這里實際是構建一個表達式樹,這樣就不需要每次去通過反射創建對象了 List
<Type> types = GetConstructor(); Object[] paramters = types.ConvertAll(item => typePairs[item].GetCache(typePairs)).ToArray(); _obj = Activator.CreateInstance(_type, paramters); } switch (_typeEnum){...} }

7.測試

    public class ConstructorIOCTest
    {
        private readonly ITestTransient m_test;
        public ConstructorIOCTest(ITestTransient test)
        {
            m_test = test;
        }
        public void WriteTestTransient()
        {
            m_test.Write();
            Console.WriteLine("--------------ConstructorIOCTest-----------");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            InitA();
            InitB();
            Console.Read();
        }
        public static void InitA()
        {
            IServiceCollection collection = new ServiceCollection();
            collection.AddTransient<ITestTransient, TestATransient>();
            collection.AddTransient<ConstructorIOCTest, ConstructorIOCTest>();
            IServiceProvider provider =  collection.BuildServiceProvider();
            provider.GetRequiredService<ConstructorIOCTest>().WriteTestTransient();
        }
        public static void InitB()
        {
            IServiceCollection collection = new ServiceCollection();
            collection.AddTransient<ITestTransient, TestBTransient>();
            collection.AddTransient<ConstructorIOCTest, ConstructorIOCTest>();
            IServiceProvider provider = collection.BuildServiceProvider();
            provider.GetRequiredService<ConstructorIOCTest>().WriteTestTransient();
        }
    }

 

 可以看出來,所有的IOC都是從構造器出發,這樣就避免到處修改的尷尬了

總結:

1.這是一個簡單的IOC代碼,里面我盡量采用最簡單的小白的方式去實現,沒有使用設計模式(本身最多有個工廠模式),沒有表達式樹,沒有鎖(鎖是非常重要的,后期我會花幾個章節去介紹各種鎖)

2.IOC其實就是一個概念,理解之后,在構造的時候添加幾個特性,比如屬性注入,方法注入,其實無非就是在ServiceTypeCache類中添加構造器,方法,屬性篩選之類的語法糖而已

3.這里沒有時間Scopd的生命周期,因為我並不是很確定.net core中這個的寫法,對我來說有2種,一種是在GetService時候,HttpContext注入,一種是將ServiceProvider里面進行包裝一層Guid,相同的Guid的Scopd相同

4.希望大家可以去看看源碼,尤其是推薦微軟開源的幾個框架,代碼之精華,越看越覺得代碼之美,雖然里面很多代碼就是在打補丁,坑死人

5.https://github.com/BestHYC/IOCSolution.git,源碼,代碼的話我就不加工了,因為沒什么好加工的,畢竟IOC實在太成熟了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM