1. 程式人生 > >白話系列之IOC,三個類實現簡單的Ioc

白話系列之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實在太成熟了

&n