.NET中值得體驗(yàn)的精妙設(shè)計(jì)
MEF(Managed Extensibility Framework)是.NET Framework 4.0一個(gè)重要的庫(kù),Visual Studio 2010 Code Editor的擴(kuò)展支持也是基于MEF構(gòu)建的。MEF的目標(biāo)是簡(jiǎn)化創(chuàng)建可擴(kuò)展的應(yīng)用程序,其核心類(lèi)是ComposablePart,即具有組合能力的組件,每一個(gè)稱(chēng)為ComposablePart(中文可為可組合構(gòu)件,不過(guò)下文一直采用英文來(lái)表示,這樣比較貼切)的組件可以組合(稱(chēng)為Import)其它組件的功能(其它組件通過(guò)聲明Export提供功能)并且它也可以通過(guò)定義Export將其功能暴露給其它組件。ComposablePart通過(guò)組件目錄(ComposablePartCatalog)來(lái)搜索發(fā)現(xiàn)需要的功能,組件目錄可以是一個(gè)物理文件目錄、網(wǎng)絡(luò)存儲(chǔ)等。每一個(gè)ComposablePart還具備動(dòng)態(tài)組合的能力,在必要的情況下可以重新組合功能。本文將采用自底向上的思路體驗(yàn)一下MEF的設(shè)計(jì)思想。
1、無(wú)廢話(huà)MEF
MEF的核心是可組合組件ComposablePart,它由ComposablePartDefintion來(lái)描述和創(chuàng)建。每一個(gè)可組合組件通過(guò)定義ExportDefintion向其它組件提供功能,通過(guò)ImportDefinition引用其它組件的功能,通過(guò)Metadata來(lái)描述組件自身的信息。在創(chuàng)建一個(gè)ComposablePart組件后,通過(guò)在組件目錄(ComposableCatalog)搜索需要的功能實(shí)現(xiàn)組件組合。
2、典型的MEF組合過(guò)程
(1)創(chuàng)建組件目錄(如AssemblyCatalog)
(2)創(chuàng)建組合容器CompositionContainer,組件容器通過(guò)組件目錄搜索組件的定義
(3)創(chuàng)建一個(gè)組件
(4)從組件容器獲取其它組件功能的定義,然后執(zhí)行匹配組合
示例代碼如下:
- var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); //創(chuàng)建一個(gè)程序集目錄,用于從一個(gè)程序集獲取所有的組件定義
- var container = new CompositionContainer(catalog); //創(chuàng)建一個(gè)組合容器
- var composablePart = new MyComponent();
- container.ComposeParts(composablePart); //執(zhí)行組合,從容器中獲取ExportDefinition并創(chuàng)建實(shí)例組合在一起
- // composablePart組合完成以供使用
其原理如下圖(來(lái)自mef.codeplex.com官方網(wǎng)站):
3 MEF本質(zhì)——組合基元
組合基元是對(duì)提供具有可擴(kuò)展、可組合能力的組件的“本質(zhì)”支持,它處于MEF的最底層,是整個(gè)Framework的核心類(lèi),由6個(gè)類(lèi)構(gòu)成,如下圖所示(該圖來(lái)自MEF白皮書(shū),白皮書(shū)有點(diǎn)抽象,不過(guò)看起來(lái)很過(guò)癮,后面附上本人翻譯的中文版)。
組合基元類(lèi)的描述如下:
(1)ComposablePart:即可組合組件,是組合基元的核心類(lèi)。ExportDefinitions表示該組件提供的功能的描述;而ImportDefinitions則是對(duì)引用其它組件功能的約束的描述。Metadata是對(duì)組件自身的特殊標(biāo)識(shí),當(dāng)一個(gè)ComposablePart通過(guò)Import引用其它組件功能時(shí),元數(shù)據(jù)可能作為滿(mǎn)足引用功能的約束的一個(gè)條件。
(2)ExportDefinition:定義ComposablePart向其它組件提供的功能,這個(gè)功能使用一個(gè)ContactName和Metadata來(lái)描述。ContactName即使用這個(gè)功能的契約,Metadata用于進(jìn)一步描述這個(gè)功能。
(3)ImportDefinition:定義ComposablePart對(duì)其它組件提供的功能的引用,即引用了另一個(gè)組件的Exports。ImportDefintion使用一個(gè)表達(dá)式來(lái)描述約束,它在Constraint這個(gè)屬性定義,其類(lèi)型為Expression>。這個(gè)表達(dá)式用于對(duì)一個(gè)ExportDefintion做匹配判定,其匹配方法如下:
以下是代碼片段:
- var allExportDefs = …// 從ComposablePartCatalog獲取所有ExportDefinition
- var constraintDelegate= Constraint.Compile(); //編譯成匹配函數(shù)的代理
- var satisfiedExportDefs = allExportDefs .FindAll(constraintDelegate); //使用匹配函數(shù)的代理來(lái)過(guò)濾所有的ExportDefs
(4)ComposableDefinition:即ComposablePart定義,是ComposablePart的工廠,該類(lèi)定義了一類(lèi)ComposablePart引用的功能、暴露的功能及其自身的元數(shù)據(jù)。引用的功能在ImportDefinitions中描述,暴露的功能通過(guò)ExportDefinitions描述。而Metadata則是對(duì)組件自身的描述,在MEF中一般用于在一個(gè)組件引用(Import)另一個(gè)組件功能時(shí),通過(guò)對(duì)另一個(gè)組件的元數(shù)據(jù)進(jìn)行匹配,從而來(lái)確定是否要組合另一個(gè)組件提供的功能。該類(lèi)是ComposablePart的工廠,提供了CreatePart方法?! ?/p>
(5)ComposablePartCatalog:可組合組件目錄,用于發(fā)現(xiàn)組件,這些組件可能來(lái)自物理目錄、網(wǎng)絡(luò)存儲(chǔ)等。
#p#
4 、如何使用MEF
在上面,我們描述了MEF的核心——組合基元,組合基元聽(tīng)起來(lái)很簡(jiǎn)單,很容易理解,但是想直接使用組合基元來(lái)編寫(xiě)一個(gè)ComposablePartDefinition卻不是那么容易了,在MEF的實(shí)現(xiàn),這些類(lèi)都是一些抽象類(lèi),用于描述整個(gè)可擴(kuò)展框架的模型。我先不想說(shuō)明白MEF到底是如何來(lái)使用組合基元,先看示例好了。
4.1 定義ComposablePartDefinition
MEF通過(guò)引入一個(gè)基于特性的編程模型來(lái)簡(jiǎn)化ComposablePart的定義,如下所示的MessageSender和Processor類(lèi)均是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、 創(chuàng)建ComposablePart
以下是代碼片段:
- var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); //創(chuàng)建一個(gè)程序集目錄,用于從一個(gè)程序集獲取所有的組件定義
- var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); //創(chuàng)建組件目錄
- var container = new CompositionContainer(assemblyCatalog); //創(chuàng)建組合容器
- var processorPart = new Processor();
- container.ComposeParts(processorPart); //執(zhí)行組合
- processorPart.Send();
- Console.ReadLine();
4.3 、基于特性編程模型的本質(zhì)
通過(guò)4.1和4.2的示例可以發(fā)現(xiàn),MessageSender和Processor這兩個(gè)類(lèi)型就是ComposablePartDefintion的實(shí)現(xiàn),在這兩個(gè)類(lèi)型,我們通過(guò)Export和Import(ImportMany)特性來(lái)定義暴露的功能和引用的功能。CompositionContainer通過(guò)這兩個(gè)類(lèi)所在的程序集的組件目錄來(lái)搜索所有的可組合組件定義,然后在執(zhí)行組合時(shí)利用這些定義創(chuàng)建Export對(duì)象,根據(jù)Import聲明的約束契約實(shí)現(xiàn)組件的組合。
在這個(gè)編程模型里面,它允許我們:(1)使用傳統(tǒng)OOP的類(lèi)型定義來(lái)定義一個(gè)ComposablePartDefinition,毋庸置疑,這基本沒(méi)有引入復(fù)雜的概念;(2)使用Export/Import/ImportMany等元數(shù)據(jù)來(lái)聲明組合功能,非常的簡(jiǎn)單且容易理解。
CompositionContainer將會(huì)在后臺(tái)構(gòu)建這個(gè)Part對(duì)應(yīng)的ComposablePartDefinition以及組件目錄其它ComposablePartDefinition,在執(zhí)行組合時(shí),利用Definition創(chuàng)建實(shí)例執(zhí)行組合。
5、 MEF vs MAF vs Unity
在剛學(xué)習(xí)MEF時(shí),經(jīng)常會(huì)問(wèn)一個(gè)問(wèn)題,那就是MEF和MAF這樣的插件框架、和Unity這樣的IoC框架到底有什么區(qū)別。MEF與MAF(Managed Addin Framework)最大不同在于:前者關(guān)注使用非常簡(jiǎn)單的方式來(lái)支持具有很強(qiáng)靈活性的可擴(kuò)展支持,后者關(guān)注具有物理隔離、安全、多版本支持的插件平臺(tái)架構(gòu);MEF和Unity不同在于:前者強(qiáng)調(diào)組合,后者強(qiáng)調(diào)依賴(lài)注入。
6、 MEF總結(jié)
MEF有3點(diǎn)讓我非常的深刻,首先是組合基元的設(shè)計(jì),其次是基于特性的編程模型,最后是MEF的實(shí)現(xiàn)方法。
組合基元是可擴(kuò)展支持的本質(zhì),它看起來(lái)顯得非常的簡(jiǎn)單,但卻有能夠支持強(qiáng)大的功能能力并且不失靈活性。“大道至簡(jiǎn)”,不過(guò),“簡(jiǎn)”的程度確實(shí)因人而異,MEF的“簡(jiǎn)”實(shí)在讓人佩服得五體投地。這個(gè)Framework也是除了ObjectBuilder之外讓我非常喜歡的框架,查看其代碼真是讓人無(wú)比舒暢。天人之作啊!這幫人的創(chuàng)新能力太強(qiáng)悍了!
基于特性的編程模型,允許我們使用“類(lèi)的定義 + 特性聲明”的方式來(lái)定義一個(gè)具有組合能力的組件,它使得我們基于MEF編寫(xiě)組件變得非常非常的簡(jiǎn)單!這也讓我再次體會(huì)到面向上下文編程方法的魅力~,后面我也會(huì)介紹一下我原來(lái)做過(guò)的一個(gè)基于上下文思想設(shè)計(jì)的FW,和MEF的思路有點(diǎn)類(lèi)似。
MEF在實(shí)現(xiàn)時(shí),其頂層命名空間是System.ComponentModel.Composition,底下劃分了AttributeModel、Diagnostics、Hosting、Primitives、ReflectionModel命名空間。MEF的頂層命名空間定義了我們使用最多的特性,底下命名空間分別用于定義特性模型、診斷支持、MEF宿主、組合基元、反射模型,整體實(shí)現(xiàn)非常的清晰簡(jiǎn)潔!看第一眼我就愛(ài)上這玩意了!
7 、基于特性編程模型的另一個(gè)示例
我原來(lái)設(shè)計(jì)了一個(gè)基于特性的智能體編程框架。首先,我來(lái)簡(jiǎn)潔的描述什么是智能體。智能體就是軟件代理人,用軟件來(lái)模擬人類(lèi)的特性,包括智能性、主動(dòng)性、社會(huì)性、感知性等。從實(shí)現(xiàn)角度來(lái)看,一個(gè)智能體就是一個(gè)綁定了線(xiàn)程、消息隊(duì)列的對(duì)象,這個(gè)對(duì)象用線(xiàn)程來(lái)模擬人類(lèi)大腦,用消息隊(duì)列來(lái)模擬大腦記憶體。當(dāng)智能體收到一條消息時(shí),其線(xiàn)程會(huì)接管來(lái)處理。根據(jù)上述描述,大家肯定覺(jué)得使用OOP開(kāi)發(fā)智能體有點(diǎn)麻煩。OK,那下面來(lái)看看我是如何使用上下文實(shí)現(xiàn)智能體的。
7.1 使用特性來(lái)聲明一個(gè)具有感知能力和主動(dòng)性的“人”
以下是代碼片段:
- [Agent]
- public class SomePerson
- {
- [Intelligent]
- public virtual OpenTheDoor()
- {
- // 開(kāi)門(mén),主動(dòng)性方法
- }
- [Sensible(Environment.Temperature)]
- public virtual OnTemperatureChanged(SensibilityContext context)
- {
- // 當(dāng)感知到溫度變化的響應(yīng),感知性聲明
- }
- }
7.2 創(chuàng)建智能體
以下是代碼片段:
- var agentContainer = new AgentContainer();
- var agent = agentContainer.Build(); //在后臺(tái)構(gòu)建一個(gè)真正的智能體
- agent.OpenTheDoor(); //調(diào)用OpenTheDoor方法,這個(gè)調(diào)用最終會(huì)轉(zhuǎn)變成消息發(fā)送給真正的智能體由其本身來(lái)執(zhí)行,就像某人讓另一人去關(guān)門(mén)一樣,最終將由接收到消息的人去執(zhí)行關(guān)門(mén)這個(gè)動(dòng)作。
AgentFramework具有和MEF類(lèi)似的設(shè)計(jì)方法(當(dāng)然咱們的內(nèi)功和Microsoft那幫高手沒(méi)得比了),通過(guò)“定義類(lèi)型 + 聲明智能體特性”來(lái)定義智能體,這種方式簡(jiǎn)單、靈活且可擴(kuò)展性強(qiáng)!
【編輯推薦】
- .NET急速發(fā)展 初學(xué)者如何學(xué)習(xí)
- .NET 4各項(xiàng)技術(shù)的應(yīng)用前景
- 優(yōu)秀ASP.NET程序員修煉之路
- 一位.Net平臺(tái)開(kāi)源工程師的五年回望