你知道Unity IoC Container是如何创建对象的吗?

news2024/11/23 20:24:36

ty是微软P&P推出的一个开源的IoC框架,最新的官方版本是2.0。Unity之前的版本建立在一个称为ObjectBuild的组件上,熟悉EnterLib的读者,相信对ObjectBuild不会感到陌生。对于EnterLib 5.0之前的版本,ObjectBuild可以说是所有Application Block的基石。ObjectBuild提供一种扩展、可定制的对象创建方式,虽然微软官方没有将ObjectBuild和IoC联系在一起,其本质可以看成是一个IoC框架。在Unity 2.0中,微软直接将ObjectBuild(实际上是ObjectBuild的第二个版本ObjectBuild2)的绝大部分功能实现在了Unity中,而EnterLib则直接建立在Unity之上。由此可见Unity在EnterLib以及微软其他一些开源框架(比如Software Factory)中的重要地位。

之前园子里也有一些介绍EnterLib的文章,其中也不乏对Unity/ObjectBuild的介绍。虽然微软官方声称Unity是一个轻量级的IoC框架,但是并不意味着Unity会很简单。也正是因为Unity/ObjectBuild的复杂性,很多人撰文介绍Unity/ObjectBuild的时候,往往为了面面俱到,导致很多读者不知所云。最终的结果是,了解Unity/ObjectBuild的读者能够看懂,不懂的人读了还是不懂。在本篇文章中,我试着换一种介绍方式:抓住Unity/ObjectBuild最本质的东西,剔除一些细枝末节,希望以一种全新的视角让读者了解Unity的本质。

一、从管道+上下文(Pipeline+Context)模式说起

如果要说Unity Container采用的怎样的设计/架构模式的话,我的回答是“管道+上下文(Pipeline + Context)模式”(我不知道是否真的具有这样一种叫法)。在《WCF技术剖析(卷1)》中介绍Binding一章中,我曾经对该模式作了一个类比:“比如有一个为居民提供饮用水的自来水厂,它的任务就是抽取自然水源,进行必要的净化处理,最终输送到居民区。净化处理的流程可能是这样的:天然水源被汲取到一个蓄水池中先进行杂质的过滤(我们称这个池为过滤池);被过滤后的水流到第二个池子中进行消毒处理(我们称这个池为消毒池);被消毒处理的水流到第三个池子中进行水质软化处理(我们称这个池为软化池);最终水通过自来水管道流到居民的家中。”

当我们需要创建一个基础架构对某种元素(例子中需要进行处理的水)进行一系列处理的时候,我们就可以将相应的处理逻辑(例子中的过滤、消毒和软化)实现在相应“节点”(例子中的过滤池、消毒池和软化池 )中。根据需要(比如水质情况)对相应的节点进行有序组合(水质的不同决定了处理工序的差异)从而构成一个管道(自来水厂整个水处理管道)。由于每一个节点具有标准的接口,我们可以对组成管道的各个节点具有任意重组,也可以为某种需要自定义节点,从而使我们的“管道”变得能够适应所有的处理需要。

对于这样的设计,其实我们并不陌生。比如ASP.NET的运行时就可以看成是一个由若干HttpModule组成的处理HTTP请求的管道,WCF中Binding就是一个由若干信道(Channel)组成的处理Message的管道。相同的设计还体现在.NET Remoting, BizTalk等相关框架和产品的设计上。

基于相应标准的“节点”进行有序组合构成管道,但是各个相对独立的节点如何进行相应的协作呢?这就需要在整个管道范围内共享一些上下文(Context),上下文是对管道处理对象和处理环境的封装。ASP.NET运行时管道的上下文对象是HttpContext,而Binding管道的上下文是BindingContext。

二、UnityContainer是BuildStrategy的管道

作为一个IoC框架,Unity Container的最终目的动态地解析和注入依赖,最终提供(创建新对象或者提供现有对象)一个符合你要求的对象。为了让整个对象提供处理流程变得可扩展和可订制,整个处理过程被设计成一个管道。管道的每一个节点被称为BuilderStrategy,它们按照各自的策略参与到整个对象提供处理流程之中。

除了对象的提供功能之外,Unity Container还提供另一个相反的功能:对象的回收。我们暂且将两者称之为Build-Up和Tear-Down。Build-Up和Tear-Down采用相同的处理机制。

左图反映的就是Unity Container由若干BuilderStrategy组成的一个用于进行对象的Build-Up和Tear-Down的管道。每一个BuildStrategy具有相同的接口(这个接口是IBuilderStrategy),它们具有四个标准的方法:PreBuildUp、PostBuildUp、PreTearDown和PostTearDown。从名称我们不难看出,四个方法分别用于完成对象创建前/后和对象回收前后相应的操作。具体来说,当需要利用该管道创建某个对象的时候,先按照BuilderStrategy在管道中的顺序调用PreBuildUp方法,到管道底端后,按照相反的顺序调用PostBuildUp方法。

对于组成Unity Container管道的各个BuilderStrategy来说,它们彼此是相互独立的,一个BuilderStrategy只需要完成基于自身策略相应的操作,不需要知道其他BuilderStrategy的存在。只有这样才能实现对管道的灵活定制,真正实现可扩展。但是在真正工作的时候,彼此之间需要共享一些上下文以促进相互协作。在这里,BuilderContext起到了这样的作用。在Unity中,BuilderContext实现了IBuilderContext,我们不妨来看看IBuilderContext定义了些什么:

   1: public interface IBuilderContext
   2: {
   3:     //Others...
   4:     bool BuildComplete { get; set; }
   5:     NamedTypeBuildKey BuildKey { get; set; }
   6:     NamedTypeBuildKey OriginalBuildKey { get; }
   7:     object CurrentOperation { get; set; }
   8:     object Existing { get; set; }
   9: }

上面对IBuilderContext的定义中,简单起见,我刻意省略了一些属性。在上述的属性列表中,BuildComplete表示Build操作是否被标识为结束,如果某个BuilderStrategy已经完成了Build的操作,可以将其设置为True,这样后续的BuilderStrategy就可以根据该值进行相应的操作(大部分将不作进行后续的Build);BuildKey和OriginalBuildKey是一个以Type + Name为组合的代表当前Build操作(通过CurrentOperation表示)的键;Existing属性表示已经生成的对象,一般来讲BuilderStrategy会将自己生成的对象赋给该值;而Strategies则代表了整个BuilderStrategy管道。

三、创建一个最简单的BuilderStrategy

现在我们编写一个最简单不过的例子,看看UnityContainer是如何借助于BuilderStrategy管道进行对象的提供的(你可以通过这里下载源代码)。我们先来创建一个最简单不过的BuilderStrategy,我们的策略就是通过反射的方式来创建对象,为此我们将该BuilderStrategy命名为ReflectionBuilderStrategy。

   1: public class ReflectionBuilderStrategy:BuilderStrategy
   2: {
   3:     public override void PreBuildUp(IBuilderContext context)
   4:     {
   5:         if (context.BuildComplete || null != context.Existing)
   6:         {
   7:             return;
   8:         }
   9:         var value = Activator.CreateInstance(context.BuildKey.Type);
  10:         if (null != value)
  11:         {
  12:             context.Existing = value;
  13:             context.BuildComplete = true;
  14:         }
  15:     }
  16: }

ReflectionBuilderStrategy继承自统一的基类BuilderStrategy。由于我们只需要ReflectionBuildStrategy进行对象的创建,这里我们只需要重写PreBuildUp方法。在PreBuildUp方法中,如果需要提供的对象已经存在(通过BuilderContext的Existing属性判断)或者Build操作已经完成(通过BuilderContext的BuildComplete属性判断),则直接返回。否则通过BuilderContext的BuildKey属性得到需要创建对象的类型,通过反射的机制创建该对象,将其赋给BuilderContext的Existing属性,并将BuildComplete设置成True。

现在BuilderStrategy已经创建成功,如何将它添加到UnityContainer的BuilderStrategy管道呢?一般地,我们需要为BuilderStrategy创建相应的扩展对象。为此,下面我们创建了一个继承自UnityContainerExtension的类型ReflectionContainerExtension:

   1: public class ReflectionContainerExtension : UnityContainerExtension
   2: {
   3:     protected override void Initialize()
   4:     {
   5:         this.Context.Strategies.AddNew<ReflectionBuilderStrategy>(UnityBuildStage.PreCreation);
   6:     }
   7: }

在ReflectionContainerExtension中,我仅仅重写了Initialize方法。在该方法中,通过Context属性得到相应UnityContainer的BuilderStrategy管道,并调用AddNew方法将我们创建的ReflectionBuilderStrategy添加进取。泛型方法AddNew接受一个UnityBuildStage类型的枚举。UnityBuildStage代表整个Build过程的某个阶段,在这里决定了添加的BuilderStrategy在管道中的位置。现在我们假设需要通过UnityContainer来创建下面一个类型为Foo的对象:

  1: public class Foo
   2: {
   3:     public Guid Id { get; private set; }
   4:  
   5:     public Foo()
   6:     {
   7:         Id = Guid.NewGuid();
   8:     }
   9: }

真正通过UnityContainer进行对象的提供实现在下面的代码中。由于UnityContainer在初始化的过程中会通过UnityDefaultStrategiesExtension这么一个扩展,所以我特意通过调用RemoveAllExtension将其清除。然后调用AddExtension将我们上面创建的ReflectionContainerExtension添加到UnityContainer的扩展列表中。最后3次调用UnityContainer的Resolve方法得到Foo对象,并将ID输出。

   1: static void Main(string[] args)
   2: {
   3:     IUnityContainer container = new UnityContainer();
   4:     container.RemoveAllExtensions();
   5:     container.AddExtension(new ReflectionContainerExtension());
   6:     Console.WriteLine(container.Resolve<Foo>().Id);
   7:     Console.WriteLine(container.Resolve<Foo>().Id);
   8:     Console.WriteLine(container.Resolve<Foo>().Id);            
   9: }
  10:     }

下面是输出结果:

b38aa0b4-cc69-4d16-9f8c-8ea7baf1d853
ef0cefc2-ffac-4488-ad96-907fb568360b
08c538df-e208-4ef9-abe0-df7841d7ab60

四、通过自定义BuilderStrategy实现单例模式

上面的例子已经能够基本上反映出UnityContainer借助于BuilderStrategy管道的对象提供机制了。为了更加进一步的说明“管道”的存在,我们再自定义另一个简单的BuilderStrategy,实现我们熟悉的单例模式(基于UnityContainer对象来说是单例)。下面是是实现单例模式的BuilderStrategy:SingletonBuilderStrategy,和相应的Unity扩展。在SingletonBuilderStrategy中,我们通过一个静态字典用于缓存创建成功的对象,该对象在字典中的Key为创建对象的类型。被创建的对象在PostCreate方法中被缓存,在PreCreate中被返回。为了将该SingletonBuilderStrategy至于管道的前端,在添加的时候指定的UnityBuildStage为Setup。

   1: public class SingletonBuilderStrategy : BuilderStrategy
   2: {
   3:     private static IDictionary<Type, object> cachedObjects = new Dictionary<Type, object>();
   4:  
   5:     public override void PreBuildUp(IBuilderContext context)
   6:     {
   7:         if (cachedObjects.ContainsKey(context.OriginalBuildKey.Type))
   8:         {
   9:             context.Existing = cachedObjects[context.BuildKey.Type];
  10:             context.BuildComplete = true;
  11:         }
  12:     }
  13:  
  14:     public override void PostBuildUp(IBuilderContext context)
  15:     {
  16:         if (cachedObjects.ContainsKey(context.OriginalBuildKey.Type) || null == context.Existing)
  17:         {
  18:             return;
  19:         }
  20:  
  21:         cachedObjects[context.OriginalBuildKey.Type] = context.Existing;
  22:     }
  23: }
  24:  
  25: public class SingletonContainerExtension : UnityContainerExtension
  26: {
  27:     protected override void Initialize()
  28:     {
  29:         this.Context.Strategies.AddNew<SingletonBuilderStrategy>(UnityBuildStage.Setup);
  30:     }
  31: }

现在,我们将基于SingletonBuilderStrategy的扩展添加到之前的程序中。再次运行我们的程序,你会发现输出的ID都是一样的,由此可见三次创建的对象均是同一个。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         IUnityContainer container = new UnityContainer();
   6:         container.RemoveAllExtensions();
   7:         container.AddExtension(new ReflectionContainerExtension());
   8:         container.AddExtension(new SingletonContainerExtension());
   9:         Console.WriteLine(container.Resolve<Foo>().Id);
  10:         Console.WriteLine(container.Resolve<Foo>().Id);
  11:         Console.WriteLine(container.Resolve<Foo>().Id);            
  12:     }
  13: }

输出结果:

254e1636-56e7-42f7-ad03-493864824d42
254e1636-56e7-42f7-ad03-493864824d42
254e1636-56e7-42f7-ad03-493864824d42

总结:虽然Unity具体的实现机制相对复杂,但是其本质就是本文所介绍的基于BuilderStrategy+BuilderContext的Pipeline+Context的机制。当你在研究Unity的具体实现原理的时候,抓住这个原则会让你不至于迷失方向。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/644863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux:初识

1 诞生 创始人&#xff1a;林纳斯 托瓦兹 时 间&#xff1a;1991年&#xff0c;上大学期间 2 Linux的组成 linux主要由内核、系统级应用程序组成 图2-1 Linux系统结构&#xff08;图自黑马程序员课程&#xff09; 内核&#xff1a;对硬件进行调度&#xff0c;比如调度CPU、内…

CSDN原力值解析:功能作用、获取方法、积分对应等级关系详解

开篇声明&#xff1a;本博主非官方人员&#xff0c;也是非所谓的 CSDN 内容合伙人&#xff0c;所以本文博主站在一个中立的角度、以博主自身的主观观点的角度来解答 CSDN 的一个叫做 “原力值” 东西&#xff0c;本文欢迎随时在留言区讨论&#xff0c;但是拒绝硬杠&#xff0c;…

推挽电路应用

1. 推挽电路&#xff0c;常用上N下P型 2. 输出与输入同相&#xff1a; 输入低电平&#xff0c;输出低电平&#xff0c;输出受输入限制&#xff0c;输入输出同相 输入高电平&#xff0c;输出低电平&#xff0c;输出受输入限制&#xff0c;输入输出同相 3. N管烧坏原因分析 当…

华为诺亚极简网络,靠13层就拿下83%精度(附源代码)

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 论文地址&#xff1a;https://arxiv.org/pdf/2305.12972.pdf 项目代码&#xff1a;https://github.com/huawei-noah/VanillaNet ht…

【裸机开发】系统时钟分路 PLL2_PFDx、PLL3_PFDx 配置实验(二)—— 寄存器分析篇

上一篇介绍了 系统时钟的来源、时钟树 以及 PLL1 分路的配置步骤。我们注意到&#xff0c;PLL2、PLL3是固定倍频&#xff0c;无法修改&#xff0c;但是他们下分的 PFDx 分路是可以修改的。只不过我们在初始化的时候&#xff0c;依然按照官方给定的频率进行初始化。 目录 一、了…

2023年,程序员如何构建持续增长的被动收入?

大家好&#xff0c;我是晓衡&#xff01; 我致力于帮助开发者通过技术&#xff0c;实现被动收入&#xff0c;并利用复利效应获得收益最大化。 经过 6 年的探索和实践&#xff0c;取得了一定的成效。 01 起点 我是从 2018 年 7 月份开始全职做这个 Cocos 内容公众号&#xff0c;…

room数据库升级

直接添加表字段&#xff0c;不升级会报异常如上图。 1.表字段或减少表字段 2.增加表 如上等情况需要升级数据库 方法如下&#xff08;以添加表字段为例&#xff09;&#xff1a; Database( entities [XXInfo::class], version 2 // 旧版本为1 ) init { …

Linux:grep、wc命令和管道符

1、grep命令&#xff1a;从文件中根据关键词过滤文件行&#xff0c;语法&#xff1a; grep [-n] 关键词 文件路径 选项-n是可选的&#xff0c;表示在结果中输出匹配到的行的行号关键词&#xff1a;必填&#xff0c;表示要过滤的关键词文件路径&#xff1a;必填&#xff0c;表示…

海思如何编译驱动

一、安装海思的SDK 这一步在海思的说明文档中有&#xff0c;运行sdk.unpack 二、配置内核 进入osdrv/opensource/kernel/ 根据里面的说明文档&#xff0c;没有内核就去下载内核&#xff0c;如果在www.kernel.org网站下载内核十分慢&#xff0c;推荐使用镜像列表下载&#xff0c…

web动画(Animation) - 过渡效果transition

内容目录&#xff1a; 过渡动画&#xff1b;过渡动画的属性&#xff1b; 一、过渡动画 过渡&#xff08;transition&#xff09;作用&#xff1a;- 通过过渡可以指定一个属性发生变化时的切换方式- 通过过渡可以创建一些非常好的效果&#xff0c;提升用户的体验现在我们通过一…

Qt6之样式表2

一、样式选择器类型 一般情况下组件最终都会产生父子、子孙等关系&#xff0c;此时样式选择器类型非常重要&#xff0c;它决定着你的类型是否互相直接独立、互相影响和便捷高效的快速设置样式。 1、如下图常见的一个工具栏&#xff0c;切换时鼠标划过是灰色&#xff0c;选中后是…

QTYX量化系统实战案例分享|每日增量涨停股池叠加形态分析-202306第三弹

前言 “实战案例分享系列”是和大家分享一些股票量化分析工具QTYX在实战中的应用案例&#xff08;包括失败的案例&#xff09;&#xff0c;这样能够帮助大家更好地去理解QTYX中的功能设计&#xff0c;也能更好地帮助大家搭建出属于自己的量化交易系统。 关于QTYX的使用攻略可以…

给大家分享下什么是「API接口」

作为产品经理&#xff0c;了解清楚接口的相关知识是非常有必要的&#xff0c;毕竟总不想被技术大佬认为自己时什么都不懂的需求搬运工。那就往下看下去吧 -----拿去餐馆吃饭的例子 模拟网络请求流程 厨师是后端提供API&#xff0c;服务员是前端请求调用API&#xff0c;我们是用…

Pyside6-第八篇-QLabel文本标签

本篇是Pyside6的第八篇&#xff0c;本章来看看另一个知识点。 文本标签QLabel。它不仅仅可以用于文本&#xff0c;还可以显示富文本和图像。它是一个多功能的小部件&#xff0c;可以根据需要显示不同类型的内容。 部分源码 class QLabel(QFrame):"""QLabel(self…

T9481 T8300 T8110C罗克韦尔自动化可信通信接口

​ T9481 T8300 T8110C罗克韦尔自动化可信通信接口 T9481 T8300 T8110C罗克韦尔自动化可信通信接口 DCS有哪些通讯方式&#xff1f;各有什么缺点&#xff1f; dcs作为大型控制系统&#xff0c;它采用的通信方式无非就是数字通信和模拟通信。数字通信它在DCS使用就是在监视层和…

PCB封装设计实践和文件模板

在之前的文章 详解AD(Altium Designer 23)中的机械层 - 1、详解AD(Altium Designer 23)中的机械层 - 2 中&#xff0c;我们详细介绍了AD中机械层的相关设置&#xff0c;今天结合PCB封装的设计&#xff0c;给出一些实践建议&#xff0c;并分享一个文件模板。 PCB封装设计 PCB&…

SpringBoot 集成 canal

什么是 Canal 阿里巴巴 B2B 公司&#xff0c;因为业务的特性&#xff0c;卖家主要集中在国内&#xff0c;买家主要集中在国外&#xff0c;所以衍生出了同步杭州和美国异地机房的需求&#xff0c;从 2010 年开始&#xff0c;阿里系公司开始逐步的尝试基于数据库的日志解析&#…

Linux常用指令和知识

ls 显示工作目录底下的所有文件/文件夹 使用命令ls, 会直接显示HOME目录下的所有文件 如果不加任何参数,那么ls指定的目录是初始的HOME目录(因为初始的工作目录为HOME目录): 使用ls / 会显示根目录底下的所有文件 如何查看根目录: 三个参数: -a -h -l -a 选项表示all的意思,列…

Python 基于招聘数据可视化系统

1 简介 Python 基于招聘数据可视化系统&#xff0c;视频效果如下&#xff1a; 基于Python的招聘信息可视化系统&#xff0c;附源码 随着国内的经济不断的快速发展&#xff0c;现在学生的就业压力也在逐年增加&#xff0c;网络上的招聘信息非常的丰富&#xff0c;但是对于学生而…

百城巡展 | 人大金仓6月阔步新征程全力开新局

6月上旬&#xff0c;人大金仓“百城巡展”走过天津、杭州、成都&#xff0c;吸引线上线下逾6660人参与&#xff0c;并有14家新成员单位加入金兰生态组织&#xff0c;共同支撑用户更多关键性应用需求&#xff0c;为人大金仓开拓新市场、赋能新行业、构建新生态迈出坚实的一步。 …