手写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 ...
随机推荐
- 深入解读Dictionary
Dictionary<TKey,TValue>是日常.net开发中最常用的数据类型之一,基本上遇到键值对类型的数据时第一反应就是使用这种散列表.散列表特别适合快速查找操作,查找的效率是常数 ...
- 如何使用ABP进行软件开发之基础概览
ABP框架简述 1)简介 在.NET众多的技术框架中,ABP框架(本系列中指aspnetboilerplate项目)以其独特的魅力吸引了一群优秀开发者广泛的使用. 在该框架的赋能之下,开发者可根据需求 ...
- C# 模型赋值
/// <summary> /// 模型赋值 /// </summary> /// <param name="target">目标</pa ...
- SpringBoot2.x入门:快速创建一个SpringBoot应用
前提 这篇文章是<SpringBoot2.x入门>专辑的第2篇文章,使用的SpringBoot版本为2.3.1.RELEASE,JDK版本为1.8. 常规的套路会建议使用Spring官方提 ...
- 10大HBase常见运维工具整理
摘要:HBase自带许多运维工具,为用户提供管理.分析.修复和调试功能.本文将列举一些常用HBase工具,开发人员和运维人员可以参考本文内容,利用这些工具对HBase进行日常管理和运维. HBase组 ...
- 如何用好 IDEA ,Java 撸码效率至少提升 5 倍?
以前的Java项目中,充斥着太多不友好的代码:POJO的getter/setter/toString:异常处理:I/O流的关闭操作等等,这些样板代码既没有技术含量,又影响着代码的美观,Lombok应运 ...
- Flutter —快速开发的IDE快捷方式
老孟导读:这是老孟翻译的精品文章,文章所有权归原作者所有. 欢迎加入老孟Flutter交流群,每周翻译2-3篇付费文章,精彩不容错过. 原文地址:https://medium.com/flutter- ...
- Java基础-网络编程1
网络编程 Socket 基本概念 C/S结构 :全称为Client/Server结构,是指客户端和服务器结构.常见程序有QQ.迅雷等软件. B/S结构 :全称为Browser/Server结构,是指浏 ...
- day51 表格与表单
目录 一.表格标签 二.表单标签 1 form内最重要的就是input标签,它通过type变形 2 select标签 3 textarea标签 4 重点 三.通过flask实现表单提交 四.css 1 ...
- DVWA学习记录 PartⅢ
CSRF 1. 题目 CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie.会话等),诱骗其点击恶意链接或者 ...