IOC(控制翻转)是程序设计的一种思想,其本质就是上端对象不能直接依赖于下端对象,要是依赖的话就要通过抽象来依赖。这是什么意思呢?意思就是上端对象如BLL层中,需要调用下端对象的DAL层时不能直接调用DAl的具体实现,而是通过抽象的方式来进行调用。这样做是有一定的道理的。有这么一个场景,你们的项目本来是用Sqlserver来进行数据访问的,那么就会有一个SqlserverDal对象。BLL层调用的时候通过new SqlserverDal(),直接创建一个SqlserverDal对象进行数据访问,现在项目又要改为Mysql数据库,用MysqlDal进行数据访问。这时候就麻烦了,你的BLL层将new SqlserverDal()全部改为new MysqlDal()。同理BLL层也是这个道理。这么做,从程序的架构而言是相当不合理的,我只是想将SqlserverDal替换为MysqlDal。按道理说我只要添加MysqlDal对象就可以了。可现在的做法是还要将BLL中的new SqlserverDal()全部改一遍。这未免有点得不偿失了。这时IOC就排上用场了,IOC的核心理念就是上端对象通过抽象来依赖下端对象,那么我们在BLL中,不能直接通过new SqlserverDal()来创建一个对象,而是通过结构来声明(抽象的形式来进行依赖),当我们替换MysqlDal时我们只需让MysqlDal也继承这个接口,那么我们BLL层的逻辑就不用动了。那么现在又有一个问题,对象我们可以用接口来接收,所有子类出现的地方都可以用父类来替代,这没毛病。但对象的创建还是要知道具体的类型,还是通过之前的new SqlserverDal()这种方式创建对象。肯定是不合理的,这里我们还是依赖于细节。

那我们需要怎么处理呢?这时候IOC容器就该上场了,IOC容器可以理解为一个第三方的类,专门为我们创建对象用的,它不需要关注具体的业务逻辑,也不关注具体的细节。你只需将你需要的创建的对象类型传给它,它就能帮我们完成对象的创建。常见的IOC容器有Autofac,Unity

接触.net core的小伙伴可能对容器很熟悉,.net core中将IOC容器内置了。创建对象需要先进行注册

     public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IHomeBll,HomeBll>();
services.AddTransient<Iservice,LoginService>(); }

从上面的示例我们可以看到.net core中是通过ServiceCollection容器帮我们完成对象的创建,我们只需将接口的类型和要创建对象的类型传进去,它就能帮我们完成对象的创建。那么它的原理是啥呢,我们能不能创建自已的容器来帮我们完成对象的创建呢,让我们带着疑惑继续往下走

一.容器雏形

这里我们先不考虑那么多,我们先写一个容器,帮我们完成对象的创建工作。

   public  class HTContainer : IHTContainer
{
//创建一个Dictionary数据类型的对象用来存储注册的对象
private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
//注册方法,用接口的FullName为key值,value为要创建对象的类型
public void RegisterType<IT, T>()
{
this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
} //创建对象通过传递的类型进行匹配
public IT Resolve<IT>()
{
string key = typeof(IT).FullName;
Type type = this.TypeDictionary[key]; //获取要创建对象的类型
//这里先不考虑有参构造函数的问题,后面会逐一的解决这些问题
return (IT)Activator.CreateInstance(type); //通过反射完成对象的创建,这里我们先不考虑参数问题 }
}

简单调用

         //实例化容器对象
IHTContainer container = new HTContainer();
//注册对象
container.RegisterType<IDatabase,SqlserverDal>();
//通过容器完成对象的创建,不体现细节,用抽象完成对象的创建
IDatabase dal = container.Resolve<IDatabase>();
dal.Connection("con");

通过上边的一顿操作,我们做了什么事呢?我们完成了一个大的飞跃,通常创建对象我们是直接new一个,现在我们是通过一个第三方的容器为我们创建对象,并且我们不用依赖于细节,通过接口的类型完成对象的创建,当我们要将SqlserverDal替换为MysqlDal时,我们只需要在注册的时候将SqlserverDal替换为MysqlDal即可

 二.升级改造容器(解决参数问题)

上面我们将传统对象创建的方式,改为使用第三方容器来帮我们完成对象的创建。但这个容器考虑的还不是那么的全面,例如有参构造的问题,以及对象的依赖问题我们还没有考虑到,接下来我们继续完善这个容器,这里我们先不考虑多个构造函数的问题。这里先解决只有一个构造函数场景的参数问题

1.构造函数只有一个参数的情况

     //创建对象通过传递的类型进行匹配
public IT Resolve<IT>()
{
string key = typeof(IT).FullName;
Type type = this.TypeDictionary[key]; //获取要创建对象的类型
var ctor = type.GetConstructors()[]; //这里先考虑只有一个构造函数的场景 //一个参数的形式
var paraList = ctor.GetParameters();
var para = paraList[];
Type paraInterfaceType = para.ParameterType;
Type paraType = this.TypeDictionary[paraInterfaceType.FullName]; //还是要先获取依赖对象的类型
object oPara = Activator.CreateInstance(paraType); //创建参数中所依赖的对象
return (IT)Activator.CreateInstance(type,oPara); //创建对象并传递所依赖的对象
}

2.构造函数多参数的情况

上面我们解决了构造函数只有一个参数的问题,我们是通过构造函数的类型创建一个对象,并将这个对象作为参数传递到要实例化的对象中。那么多参数我们就需要创建多个参数的对象传递到要实例的对象中

   //创建对象通过传递的类型进行匹配
public IT Resolve<IT>()
{
string key = typeof(IT).FullName;
Type type = this.TypeDictionary[key]; //获取要创建对象的类型
var ctor = type.GetConstructors()[]; //这里先考虑只有一个构造函数的场景 //多个参数的形式
List<object> paraList = new List<object>(); //声明一个list来存储参数类型的对象
foreach (var para in ctor.GetParameters())
{
Type paraInterfaceType = para.ParameterType;
Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
object oPara = Activator.CreateInstance(paraType);
paraList.Add(oPara);
} return (IT)Activator.CreateInstance(type, paraList.ToArray()); //创建对象并传递所依赖的对象数组
}

 3.解决对象的循环依赖问题

通过上面的两步操作,我们已经能对构造函数中的参数初始化对象并传递到要实例的对象中,但这只是一个层级的。我们刚才做的只是解决了这么一个问题,假设我们要创建A对象,A对象依赖于B对象。我们做的就是创建了B对象作为参数传递给A并创建A对象,这只是一个层级的。当B对象又依赖于C对象,C对象又依赖于D对象,这么一直循环下去。这样的场景我们该怎么解决呢?下面我们将通过递归的方式来解决这一问题

      //创建对象通过传递的类型进行匹配
public IT Resolve<IT>()
{
return (IT)this.ResolveObject(typeof(IT));
} //通过递归的方式创建多层级的对象
private object ResolveObject(Type abstractType)
{
string key = abstractType.FullName;
Type type = this.TypeDictionary[key]; //获取要创建对象的类型
var ctor = type.GetConstructors()[];
//多个参数的形式
List<object> paraList = new List<object>();
foreach (var para in ctor.GetParameters())
{
Type paraInterfaceType = para.ParameterType;
Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
object oPara = ResolveObject(paraInterfaceType); //自已调用自己,实现递归操作,完成各个层级对象的创建
paraList.Add(oPara);
} return (object)Activator.CreateInstance(type, paraList.ToArray()); }

三.继续升级(考虑多个构造函数的问题)

上面我们只是考虑了只有一个构造函数的问题,那初始化的对象有多个构造函数我们该如何处理呢,我们可以像Autofac那样选择一个参数最多的构造函数,也可以像ServiceCollection那样选择一个参数的超集来进行构造,当然我们也可以声明一个特性,那个构造函数中标记了这个特性,我们就采用那个构造函数。

       //通过递归的方式创建多层级的对象
private object ResolveObject(Type abstractType)
{
string key = abstractType.FullName;
Type type = this.TypeDictionary[key]; //获取要创建对象的类型
var ctorArray = type.GetConstructors(); //获取对象的所有构造函数
ConstructorInfo ctor = null;
//判断构造函数中是否标记了HTAttribute这个特性
if (ctorArray.Count(c => c.IsDefined(typeof(HTAttribute), true)) > )
{
//若标记了HTAttribute特性,默认就采用这个构造函数
ctor = ctorArray.FirstOrDefault(c => c.IsDefined(typeof(HTAttribute), true));
}
else
{
//若都没有标记特性,那就采用构造函数中参数最多的构造函数
ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
} //多个参数的形式
List<object> paraList = new List<object>();
foreach (var para in ctor.GetParameters())
{
Type paraInterfaceType = para.ParameterType;
Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
object oPara = ResolveObject(paraInterfaceType); //自已调用自己,实现递归操作,完成各个层级对象的创建
paraList.Add(oPara);
} return (object)Activator.CreateInstance(type, paraList.ToArray()); }

上面的操作我们通过依赖注入的方式完成了对容器的升级,那么依赖注入到底是啥呢?

依赖注入(Dependency Injection,简称DI)就是构造A对象时,需要依赖B对象,那么就先构造B对象作为参数传递到A对象,这种对象初始化并注入的技术就叫做依赖注入。IOC是一种设计模式,程序架构的目标。DI是IOC的实现手段

手写IOC容器的更多相关文章

  1. 手写IOC实现过程

    一.手写ioc前基础知识 1.什么是IOC(Inversion of Control 控制反转)? IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合.更优良 ...

  2. 闭关修炼180天--手写IOC和AOP(xml篇)

    闭关修炼180天--手写IOC和AOP(xml篇) 帝莘 首先先分享一波思维导图,涵盖了一些Spring的知识点,当然这里并不全面,后期我会持续更新知识点. 在手写实现IOC和AOP之前(也就是打造一 ...

  3. 2、手写Unity容器--第一层依赖注入

    这个场景跟<手写Unity容器--极致简陋版Unity容器>不同,这里构造AndroidPhone的时候,AndroidPhone依赖于1个IPad 1.IPhone接口 namespac ...

  4. 3、手写Unity容器--第N层依赖注入

    这个场景跟<手写Unity容器--第一层依赖注入>又不同,这里构造AndroidPhone的时候,AndroidPhone依赖于1个IPad,且依赖于1个IHeadPhone,而HeadP ...

  5. 手写IOC实践

    一.IOC 1.什么是IOC? 控制反转(英语:Inversion of Control,缩写为IoC),是[面向对象编程]中的一种设计原则,可以用来减低计算机代码之间的[耦合度]其中最常见的方式叫做 ...

  6. 初学源码之——银行案例手写IOC和AOP

    手写实现lOC和AOP 上一部分我们理解了loC和AOP思想,我们先不考虑Spring是如何实现这两个思想的,此处准备了一个『银行转账」的案例,请分析该案例在代码层次有什么问题?分析之后使用我们已有知 ...

  7. 第三节:工厂+反射+配置文件(手写IOC)对缓存进行管理。

    一. 章前小节 在前面的两个章节,我们运用依赖倒置原则,分别对 System.Web.Caching.Cache和 System.Runtime.Cacheing两类缓存进行了封装,并形成了ICach ...

  8. 手写IOC框架

    1.IOC框架的设计思路 ① 哪些类需要我们的容器进行管理 ②完成对象的别名和对应实例的映射装配 ③完成运行期对象所需要的依赖对象的依赖

  9. 1、手写Unity容器--极致简陋版Unity容器

    模拟Unity容器实例化AndroidPhone 思路: 1.注册类型:把类型完整名称作为key添加到数据字典中,类型添加到数据字典的value中 2.获取实例:根据完整类型名称也就是key取出val ...

随机推荐

  1. html+css快速入门教程(5)

    练习: 1.画盒子1 2.画盒子2 3.京东特色购物 4.京东发现好货 5.京东玩3c 7.3 定位 通过使用 position 属性,我们可以选择 3 种不同类型的定位,这会影响元素框生成的方式. ...

  2. mybatis源码配置文件解析之五:解析mappers标签流程图

    前面几篇博客分析了mybatis解析mappers标签的过程,主要分为解析package和mapper子标签.补充一张解析的总体过程流程图,画的不好,多多谅解,感谢.

  3. Ubuntu apt update卡在Connecting to security.ubuntu.com解决方法

    Ubuntu操作系统运行apt update命令时会卡在Connecting to security.ubuntu.com,搭了梯子也无法解决 尝试了网络上的方法,如: https://blog.cs ...

  4. Python趣味入门4:选择往往是最重要的-条件语句

    人生处处有选择,程序也有选择,为了让程序变得更加强壮,程序员必须考虑任何情况,上一篇了解到了如何使用Python来行顺序语句的编写,我们写了一个可以输入姓名的生日祝贺程序,今天我们挑战条件语句! 1. ...

  5. 每日一题 - 剑指 Offer 37. 序列化二叉树

    题目信息 时间: 2019-06-29 题目链接:Leetcode tag:序列化 二叉树 队列 难易程度:中等 题目描述: 请实现两个函数,分别用来序列化和反序列化二叉树. 示例: 1 / \ 2 ...

  6. HTML的<Object>标签怎么用?

    <object>标签是一个HTML标签,用于在网页中显示音频,视频,图像,PDF和Flash等多媒体:它通常用于嵌入由浏览器插件处理的Flash页面元素,如Flash和Java项目.它还可 ...

  7. 分享一个集成.NET Core+Swagger+Consul+Polly+Ocelot+IdentityServer4+Exceptionless+Apollo+SkyWalking的微服务开发框架

    集成.NET Core+Swagger+Consul+Polly+Ocelot+IdentityServer4+Exceptionless+Apollo的微服务开发框架 Github源代码地址 htt ...

  8. Fetch.AI的最新发布speaks your language

    更新增强长期网络的稳定性 包括新的Etch功能,使我们的代码比以往对开发人员更加友好.我们现在支持太阳下的每一种语言,包括普通话,希腊语和希伯来语-甚至表情符号 介绍我们很高兴地宣布我们最新的技术更新 ...

  9. Layui文本框限制正整数

    <input type="text" name="Number" lay-verify="required|integer" plac ...

  10. day36 解决粘包问题

    目录 一.tcp粘包问题出现的原因 二.解决粘包问题low的办法 三.egon式解决粘包问题 四.实现并发 1 tcp 2 udp 一.tcp粘包问题出现的原因 前引: tcp的客户端与服务端进行通信 ...