手写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 ...
随机推荐
- 双缓冲显示字幕(卡拉ok字幕)
思路: 1.设置定时器SetTime,在Ontime()里面确定显示矩形的大小,让后用DrawText把字铁道矩形上面: 2. int nTextHei = dc.GetTextExtent( m_s ...
- MBA都需要学习哪些课程
MBA课程内容详解: 1.核心课程: 管理经济学.营销管理.战略管理.组织行为学.会计学.公司财务管理.人力资源管理与开发.管理与沟通.经济法.国际贸易 2.学位课程: 商务英语(一.二).管理伦理学 ...
- (win7) 在IIS6.0 中配置项目
1.进入IIS管理器 右击“计算机”->管理->服务和应用程序->Internet信息服务(IIS)管理器 2.将项目加入IIS中 网站->右击“默认网站”->添加虚拟目 ...
- Flutter 中那么多组件,难道要都学一遍?
在 Flutter 中一切皆是 组件,仅仅 Widget 的子类和间接子类就有 350 多个,整理的 Flutter组件继承关系图 可以帮助大家更好的理解学习 Flutter,回归正题,如此多的组件到 ...
- 三.接收并处理请求参数与QueryDict对象
一.get与post请求:重点看传参与接收参数 GET请求与传参 ---->url后面跟上?k1=v1&&k2=v2 POST请求与数据提交 (1)get请求:如直接在浏览 ...
- WSL中文本地化
WSL中文本地化 Windows Subsystem for Linux(简称WSL)是一个在Windows 10上能够运行原生Linux二进制可执行文件(ELF格式)的兼容层.它是由微软与Canon ...
- 什么是DevOps?该如何正确的在企业内进行实践
传统IT技术团队中通常都有多个独立的组织-开发团队.测试团队和运维团队.开发团队进行软件开发.测试团队进行软件测试,运维团队致力于部署,负载平衡和发布管理. 他们之间的职能有时重叠.有时依赖.有时候会 ...
- 推荐一款ui架构--frozenui
首页是这样定义的 移动框架 重磅出击 简单易用,轻量快捷,为移动端服务的前端框架 链接地址 http://frozenui.github.io/base.html#layout
- c++ 随机生成带权联通无向图
提示 1.请使用c++11编译运行 2.默认生成100个输出文件,文件名为data1.in到data100.in,如有需要自行修改 3.50000以下的点1s内可以运行结束,50000-300000的 ...
- Spring 5.2.x 源码环境搭建(Windows 系统环境下)
前期准备 1.确保本机已经安装好了 Git 2.Jdk 版本至少为 1.8 3.安装好 IntelliJ IDEA (其他开发工具,如 eclipse.Spring Tool Suite 等也是可以的 ...