• 白话系列之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实在太成熟了

  • 相关阅读:
    蓝桥杯如何训练?(附VIP题库)
    scratch2.0的教材视频,王木头系列
    out文件 dev c++
    MongoDB 学习笔记
    golang 学习笔记 -- struct interface的使用
    goang学习笔记---struct
    golang 学习笔记 ---JSON
    golang学习笔记 ---rand
    golang学习笔记 --go test
    golang学习笔记---string && strconv
  • 原文地址:https://www.cnblogs.com/yichaohong/p/11528961.html
Copyright © 2020-2023  润新知