手写IOC容器
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容器的更多相关文章
- 手写IOC实现过程
一.手写ioc前基础知识 1.什么是IOC(Inversion of Control 控制反转)? IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合.更优良 ...
- 闭关修炼180天--手写IOC和AOP(xml篇)
闭关修炼180天--手写IOC和AOP(xml篇) 帝莘 首先先分享一波思维导图,涵盖了一些Spring的知识点,当然这里并不全面,后期我会持续更新知识点. 在手写实现IOC和AOP之前(也就是打造一 ...
- 2、手写Unity容器--第一层依赖注入
这个场景跟<手写Unity容器--极致简陋版Unity容器>不同,这里构造AndroidPhone的时候,AndroidPhone依赖于1个IPad 1.IPhone接口 namespac ...
- 3、手写Unity容器--第N层依赖注入
这个场景跟<手写Unity容器--第一层依赖注入>又不同,这里构造AndroidPhone的时候,AndroidPhone依赖于1个IPad,且依赖于1个IHeadPhone,而HeadP ...
- 手写IOC实践
一.IOC 1.什么是IOC? 控制反转(英语:Inversion of Control,缩写为IoC),是[面向对象编程]中的一种设计原则,可以用来减低计算机代码之间的[耦合度]其中最常见的方式叫做 ...
- 初学源码之——银行案例手写IOC和AOP
手写实现lOC和AOP 上一部分我们理解了loC和AOP思想,我们先不考虑Spring是如何实现这两个思想的,此处准备了一个『银行转账」的案例,请分析该案例在代码层次有什么问题?分析之后使用我们已有知 ...
- 第三节:工厂+反射+配置文件(手写IOC)对缓存进行管理。
一. 章前小节 在前面的两个章节,我们运用依赖倒置原则,分别对 System.Web.Caching.Cache和 System.Runtime.Cacheing两类缓存进行了封装,并形成了ICach ...
- 手写IOC框架
1.IOC框架的设计思路 ① 哪些类需要我们的容器进行管理 ②完成对象的别名和对应实例的映射装配 ③完成运行期对象所需要的依赖对象的依赖
- 1、手写Unity容器--极致简陋版Unity容器
模拟Unity容器实例化AndroidPhone 思路: 1.注册类型:把类型完整名称作为key添加到数据字典中,类型添加到数据字典的value中 2.获取实例:根据完整类型名称也就是key取出val ...
随机推荐
- 入门大数据---Python基础
前言 由于AI的发展,包括Python集成了很多计算库,所以淡入了人们的视野,成为一个极力追捧的语言. 首先概括下Python中文含义是蟒蛇,它是一个胶水语言和一个脚本语言,胶水的意思是能和多种语言集 ...
- volatile与lock前缀指令
前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用. 本文详细解读一下v ...
- python文件处理-检查文件名/路径是否正确
内容涉及:检查路径是否存在,文件名长度是否一直,将重复的文件夹重命名 # -*- coding: utf-8 -*- import os import sys import numpy as np i ...
- DOM-BOM-EVENT(3)
3.Node常用属性 childNodes 获取所有子节点 <div id="wrap"> <div>1111</div> <div> ...
- 解决IE浏览器中点击按钮上传无效的问题
前几天写了上传功能,点击按钮上传,在谷歌中是没有任何问题的: 但是在IE浏览器中点击没有任何效果 源代码如下: 后来发现在Firefox.IE浏览器中button标签内部可以含有其他标签,但是不能对 ...
- Emergency Evacuation,题解
题目: 题意: 在某一秒,每个人可以进行一个移动:去旁边座位,去过道,在过道向出口走,求最少多少秒可以让所有人离开(具体如图和样例). 分析: 首先,我们先考虑简单的,只考虑出口前有什么事件发生:1. ...
- PE文件格式详解(五)
0x00 前言 前一篇了解了区块虚拟地址和文件地址转换的相关知识,这一篇该把我们所学拿出来用用了.这篇我们将了解更为重要的一个知识点——输入表和输出表的知识. 0x01 输入表 首先我们有疑问.这个输 ...
- es6 模块与commonJS的区别
在刚接触模块化开发的阶段,我总是容易将export.import.require等语法给弄混,今天索性记个笔记,将ES6 模块知识点理清楚 未接触ES6 模块时,模块开发方案常见的有CommonJS. ...
- classpath路径(转)
src不是classpath, WEB-INF/classes,lib才是classpathWEB-INF/ 是资源目录, 客户端不能直接访问, 这话是没错,不过现在的IDE编译器在编译时会把src下 ...
- 数据可视化之powerBI入门(十三)CALCULATE函数的最佳搭档:FILTER
https://zhuanlan.zhihu.com/p/64383000 介绍过CALCULATE函数之后,有必要再介绍它的最佳搭档:FILTER函数. CALCULATE函数的第二个及之后的参数是 ...