.NET中值得体验的精妙设计
转自: http://developer.51cto.com/art/201104/255455_all.htm
.NET 是 Microsoft XML Web services 平台。MEF是.NET Framework 4.0一个重要的库,Visual Studio 2010 Code Editor的扩展支持也是基于MEF构建的。下面让我们一起来看。
MEF(Managed Extensibility Framework)是.NET Framework 4.0一个重要的库,Visual Studio 2010 Code Editor的扩展支持也是基于MEF构建的。MEF的目标是简化创建可扩展的应用程序,其核心类是ComposablePart,即具有组合能力的组件,每一个称为ComposablePart(中文可为可组合构件,不过下文一直采用英文来表示,这样比较贴切)的组件可以组合(称为Import)其它组件的功能(其它组件通过声明Export提供功能)并且它也可以通过定义Export将其功能暴露给其它组件。ComposablePart通过组件目录(ComposablePartCatalog)来搜索发现需要的功能,组件目录可以是一个物理文件目录、网络存储等。每一个ComposablePart还具备动态组合的能力,在必要的情况下可以重新组合功能。本文将采用自底向上的思路体验一下MEF的设计思想。
1、无废话MEF
MEF的核心是可组合组件ComposablePart,它由ComposablePartDefintion来描述和创建。每一个可组合组件通过定义ExportDefintion向其它组件提供功能,通过ImportDefinition引用其它组件的功能,通过Metadata来描述组件自身的信息。在创建一个ComposablePart组件后,通过在组件目录(ComposableCatalog)搜索需要的功能实现组件组合。
2、典型的MEF组合过程
(1)创建组件目录(如AssemblyCatalog)
(2)创建组合容器CompositionContainer,组件容器通过组件目录搜索组件的定义
(3)创建一个组件 ComposablePart
(4)从组件容器获取其它组件功能的定义,然后执行匹配组合
示例代码如下:
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); //创建一个程序集目录,用于从一个程序集获取所有的组件定义
var container = new CompositionContainer(catalog); //创建一个组合容器
var composablePart = new MyComponent();
container.ComposeParts(composablePart); //执行组合,从容器中获取ExportDefinition并创建实例组合在一起
// composablePart组合完成以供使用
其原理如下图(来自mef.codeplex.com官方网站):
3 MEF本质——组合基元
组合基元是对提供具有可扩展、可组合能力的组件的“本质”支持,它处于MEF的最底层,是整个Framework的核心类,由6个类构成,如下图所示(该图来自MEF白皮书,白皮书有点抽象,不过看起来很过瘾,后面附上本人翻译的中文版)。
组合基元类的描述如下:
(1)ComposablePart:即可组合组件,是组合基元的核心类。ExportDefinitions表示该组件提供的功能的描述;而ImportDefinitions则是对引用其它组件功能的约束的描述。Metadata是对组件自身的特殊标识,当一个ComposablePart通过Import引用其它组件功能时,元数据可能作为满足引用功能的约束的一个条件。
(2)ExportDefinition:定义ComposablePart向其它组件提供的功能,这个功能使用一个ContractName和Metadata来描述。ContractName即使用这个功能的契约,Metadata用于进一步描述这个功能。
(3)ImportDefinition:定义ComposablePart对其它组件提供的功能的引用,即引用了另一个组件的Exports。ImportDefintion使用一个表达式来描述约束,它在Constraint这个属性定义,其类型为Expression>。这个表达式用于对一个ExportDefintion做匹配判定,其匹配方法如下:
以下是代码片段:
var allExportDefs = …// 从ComposablePartCatalog获取所有ExportDefinition
var constraintDelegate= Constraint.Compile(); //编译成匹配函数的代理
var satisfiedExportDefs = allExportDefs .FindAll(constraintDelegate); //使用匹配函数的代理来过滤所有的ExportDefs
(4)ComposableDefinition:即ComposablePart定义,是ComposablePart的工厂,该类定义了一类ComposablePart引用的功能、暴露的功能及其自身的元数据。引用的功能在ImportDefinitions中描述,暴露的功能通过ExportDefinitions描述。而Metadata则是对组件自身的描述,在MEF中一般用于在一个组件引用(Import)另一个组件功能时,通过对另一个组件的元数据进行匹配,从而来确定是否要组合另一个组件提供的功能。该类是ComposablePart的工厂,提供了CreatePart方法。
(5)ComposablePartCatalog:可组合组件目录,用于发现组件,这些组件可能来自物理目录、网络存储等。
4 、如何使用MEF
在上面,我们描述了MEF的核心——组合基元,组合基元听起来很简单,很容易理解,但是想直接使用组合基元来编写一个ComposablePartDefinition却不是那么容易了,在MEF的实现,这些类都是一些抽象类,用于描述整个可扩展框架的模型。我先不想说明白MEF到底是如何来使用组合基元,先看示例好了。
4.1 定义ComposablePartDefinition
MEF通过引入一个基于特性的编程模型来简化ComposablePart的定义,如下所示的MessageSender和Processor类均是ComposablePart定义。
以下是代码片段:
public class MessageSender
{
[Export("MessageSender")]
public void Send(string message)
{
Console.WriteLine(message);
}
} [Export]
public class Processor
{
[Import("MessageSender")]
public Action MessageSender { get; set; }
public void Send()
{
MessageSender("Processed");
}
}
4.2、 创建ComposablePart
以下是代码片段:
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); //创建一个程序集目录,用于从一个程序集获取所有的组件定义
var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); //创建组件目录
var container = new CompositionContainer(assemblyCatalog); //创建组合容器
var processorPart = new Processor();
container.ComposeParts(processorPart); //执行组合
processorPart.Send();
Console.ReadLine();
4.3 、基于特性编程模型的本质
通过4.1和4.2的示例可以发现,MessageSender和Processor这两个类型就是ComposablePartDefintion的实现,在这两个类型,我们通过Export和Import(ImportMany)特性来定义暴露的功能和引用的功能。CompositionContainer通过这两个类所在的程序集的组件目录来搜索所有的可组合组件定义,然后在执行组合时利用这些定义创建Export对象,根据Import声明的约束契约实现组件的组合。
在这个编程模型里面,它允许我们:
(1)使用传统OOP的类型定义来定义一个ComposablePartDefinition,毋庸置疑,这基本没有引入复杂的概念;
(2)使用Export/Import/ImportMany等元数据来声明组合功能,非常的简单且容易理解。
CompositionContainer将会在后台构建这个Part对应的ComposablePartDefinition以及组件目录其它ComposablePartDefinition,在执行组合时,利用Definition创建实例执行组合。
5、 MEF vs MAF vs Unity
在刚学习MEF时,经常会问一个问题,那就是MEF和MAF这样的插件框架、和Unity这样的IoC框架到底有什么区别。MEF与MAF(Managed Addin Framework)最大不同在于:前者关注使用非常简单的方式来支持具有很强灵活性的可扩展支持,后者关注具有物理隔离、安全、多版本支持的插件平台架构;MEF和Unity不同在于:前者强调组合,后者强调依赖注入。
6、 MEF总结
MEF有3点让我非常的深刻,首先是组合基元的设计,其次是基于特性的编程模型,最后是MEF的实现方法。
组合基元是可扩展支持的本质,它看起来显得非常的简单,但却有能够支持强大的功能能力并且不失灵活性。“大道至简”,不过,“简”的程度确实因人而异,MEF的“简”实在让人佩服得五体投地。这个Framework也是除了ObjectBuilder之外让我非常喜欢的框架,查看其代码真是让人无比舒畅。天人之作啊!这帮人的创新能力太强悍了!
基于特性的编程模型,允许我们使用“类的定义 + 特性声明”的方式来定义一个具有组合能力的组件,它使得我们基于MEF编写组件变得非常非常的简单!这也让我再次体会到面向上下文编程方法的魅力~,后面我也会介绍一下我原来做过的一个基于上下文思想设计的FW,和MEF的思路有点类似。
MEF在实现时,其顶层命名空间是System.ComponentModel.Composition,底下划分了AttributeModel、Diagnostics、Hosting、Primitives、ReflectionModel命名空间。MEF的顶层命名空间定义了我们使用最多的特性,底下命名空间分别用于定义特性模型、诊断支持、MEF宿主、组合基元、反射模型,整体实现非常的清晰简洁!看第一眼我就爱上这玩意了!
7 、基于特性编程模型的另一个示例
我原来设计了一个基于特性的智能体编程框架。首先,我来简洁的描述什么是智能体。智能体就是软件代理人,用软件来模拟人类的特性,包括智能性、主动性、社会性、感知性等。从实现角度来看,一个智能体就是一个绑定了线程、消息队列的对象,这个对象用线程来模拟人类大脑,用消息队列来模拟大脑记忆体。当智能体收到一条消息时,其线程会接管来处理。根据上述描述,大家肯定觉得使用OOP开发智能体有点麻烦。OK,那下面来看看我是如何使用上下文实现智能体的。
7.1 使用特性来声明一个具有感知能力和主动性的“人”
以下是代码片段:
[Agent]
public class SomePerson
{
[Intelligent]
public virtual OpenTheDoor()
{
// 开门,主动性方法
}
[Sensible(Environment.Temperature)]
public virtual OnTemperatureChanged(SensibilityContext context)
{
// 当感知到温度变化的响应,感知性声明
}
}
7.2 创建智能体
以下是代码片段:
var agentContainer = new AgentContainer();
var agent = agentContainer.Build(); //在后台构建一个真正的智能体
agent.OpenTheDoor(); //调用OpenTheDoor方法,这个调用最终会转变成消息发送给真正的智能体由其本身来执行,就像某人让另一人去关门一样,最终将由接收到消息的人去执行关门这个动作。
AgentFramework具有和MEF类似的设计方法(当然咱们的内功和Microsoft那帮高手没得比了),通过“定义类型 + 声明智能体特性”来定义智能体,这种方式简单、灵活且可扩展性强!
.NET中值得体验的精妙设计的更多相关文章
- MEF——.NET中值得体验的精妙设计
摘要:.NET 是 Microsoft XML Web services 平台.MEF是.NET Framework 4.0一个重要的库,Visual Studio 2010 Code Editor的 ...
- APP开发中,如何从UI设计上提升APP用户体验
设计中有很多细微的东西要注意,就如UI设计中,元素的统一性,图标风格.段落的排版等等,只有能注意这些细节,你的 APP UI 才算合格. 干货君总结了17个提升用户体验的 UI 设计小技巧,也是我们日 ...
- 抓到 Netty 一个隐藏很深的内存泄露 Bug | 详解 Recycler 对象池的精妙设计与实现
欢迎关注公众号:bin的技术小屋,如果大家在看文章的时候发现图片加载不了,可以到公众号查看原文 本系列Netty源码解析文章基于 4.1.56.Final版本 最近在 Review Netty 代码的 ...
- C++11 中值得关注的几大变化(网摘)
C++11 中值得关注的几大变化(详解) 原文出处:[陈皓 coolshell] 源文章来自前C++标准委员会的 Danny Kalev 的 The Biggest Changes in C++11 ...
- 使用Bootstrap3和Ladda UI实现的多种按钮“加载中”效果体验
在线演示 在线演示 大家在开发基于web的网站或者web应用中,常常在AJAX调用的过程中需要提示用户并且展示相关的“加载中”效果,类似的UI设计也非常多,比如,当点击一个按钮后,在它的旁边显示一个 ...
- 在APP开发中,如何优雅的设计APP页面
1.明确页面设计在整个产品设计中的位置 互联网产品设计的流程大致是:产品定位——需求分析——信息架构设计——流程设计——页面框架设计——设计说明——输出设计文档.可以看到页面设计是处于整个流程的后期, ...
- Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析
Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析 生鲜电商搜索引擎的特点 众所周知,标准的搜索引擎主要分成三个大的部分,第一步是爬虫系统,第二步是数据分析,第三步才 ...
- Spark小课堂Week7 从Spark中一个例子看面向对象设计
Spark小课堂Week7 从Spark中一个例子看面向对象设计 今天我们讨论了个问题,来设计一个Spark中的常用功能. 功能描述:数据源是一切处理的源头,这次要实现下加载数据源的方法load() ...
- 在java中String类为什么要设计成final
在java中String类为什么要设计成final? - 胖胖的回答 - 知乎 https://www.zhihu.com/question/31345592/answer/114126087
随机推荐
- input type=file
(1)首先来说一下,如何让 <input type='file' >成为你想要的模样. 最简单的方法就是在让<input type='file' >的透明度为0(完全透明),然 ...
- 海量字符串查找——bloom filter,c
对于海量字符串的查找,一般有两种方法,一种是建树,还有一种就是bf算法,即布隆过滤器,这个从原来上讲比较简单,也易于实现,主要就是根据哈希算法来实现. int len(char *ch) { int ...
- spark sql 访问hive数据时找不mysql的解决方法
我尝试着在classpath中加n入mysql的驱动仍不行 解决方法:在启动的时候加入参数--driver-class中加入mysql 驱动 [hadoop@master spark-1.0.1-bi ...
- UVALive-4329 Ping pong (树状数组)
题目大意:有n个数排成一列,问从中能找出几个三元组(ai,aj,ak)满足i<j<k并且这三个数严格单调. 题目分析:枚举中间的数字aj,如果aj前面有c(j)个数a(j)小,后面有d(j ...
- C/C++语言void及void指针深层探索(转)
转自:http://www.lanou3g.com/blog/sort/SelfiOS/page/78 1.概述 许多初学者对C/C++语言中的void及void指针类型不甚理解,因此在使用上出现了一 ...
- Linux驱动设计—— 部分系统调用函数原型
cdev结构体和它的初始化注册函数原型 struct cdev { struct kobject kobj; // 每个 cdev 都是一个 kobject struct m ...
- 黑马程序员——JAVA基础之内部类,匿名内部类
------- android培训.java培训.期待与您交流! ---------- 内部类 将一个类定义在另一个类的里面,对里面那个类就称为内部类(内置类,嵌套类). 内部类访问特点: • 内部 ...
- Android Studio 常用快捷键以及设置
常用快捷键: Ctrl+Q 出现文档提示 跟ecplise的 鼠标悬浮差不多 Ctrl+Alt+t 包围代码 Home End 移动光标到文本首和文本尾 Alt+回车 导入当前包 Ctrl+Alt+O ...
- Java Warmup
http://www.brendangregg.com/blog/2016-09-28/java-warmup.html
- Linux下tmpfs介绍及使用
tmpfs介绍 tmpfs是一种虚拟内存文件系统,而不是块设备.是基于内存的文件系统,创建时不需要使用mkfs等初始化它最大的特点就是它的存储空间在VM(virtual memory),VM是由lin ...