概述
Managed Extensibility Framework(MEF)是.NET平台下的一個擴充性管理架構,它是一系列特性的集合,包括依賴注入(DI)以及Duck Typing等。MEF為開發人員提供了一個工具,讓我們可以輕鬆的對應用程式進行擴充並且對已有的代碼產生最小的影響,開發人員在開發過程中根據功能要求定義一些擴充點,之後擴充人員就可以使用這些擴充點與應用程式互動;同時MEF讓應用程式與擴充程式之間不產生直接的依賴,這樣也允許在多個具有同樣的擴充需求之間共用擴充程式。
本文將介紹一下Managed Extensibility Framework的一些簡單使用。
簡單依賴注入
大家可以去這裡http://code.msdn.microsoft.com/mef下載MEF的CTP版本,在下載包裡有一些簡單的文檔和樣本。下面先來看一個簡單的樣本,這裡輸出一個字串:
Code 1
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleestatic void Main(string[] args){ Console.WriteLine("This is a simple string.");}
現在考慮到該字串將來可能發生變化,不知道該字串將從何處取得,也就是說這裡有可能是一個變化點,也是一個擴充點,那我們現在使用MEF對其進行重新設計,我們將會把這個過程分成兩個部分,一部分用於提供字串,而另一部分則用來使用字串,定義一個字串提供者,大家注意到這裡為OutputTitle屬性添加了一個Export特性,這標識著此處為一個輸出,它使的MEF能夠對其進行識別,如下代碼所示:
Code 2
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleepublic class WeekStringProvider{ [Export("Caption")] public String OutputTitle { get { return "星期六"; } }}
這裡只是定義了一個簡單的屬性,其實在同一個類型可以定義多個輸出,Export同時指定了一個字串的契約名稱,這意味著任何匹配契約名稱的程式都可以使用該擴充。除此之外,我們還可以指定一個類型來代替字串的契約名稱,後面會說到。我們再定義一個輸入,即用來消費該字串,同樣是一個簡單的屬性,不過這次添加的是Import特性:
Code 3
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleepublic class Client{ [Import("Caption")] public String OutputTitle { get; set; }}
現在有了輸出和輸入,就可以在主程式中進行調用了,需要建立一個CompositionContainer容器,並添加所有的組件到該容器中,再調用它的Bind()方法,一旦調用該方法後,就可以使用所有的組件了,如下代碼所示:
Code 4
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleestatic void Main(string[] args){ Client client = new Client(); CompositionContainer container = new CompositionContainer(); container.AddComponent<Client>(client); container.AddComponent<WeekStringProvider>(new WeekStringProvider()); container.Bind(); Console.WriteLine(client.OutputTitle);}
輸出結果如所示:
現在我們再定義另外一個擴充程式,讓它返回一個日期文字,如下代碼所示:
Code 5
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleepublic class DateStringProvider{ [Export("Caption")] public String OutputTitle { get { return DateTime.Now.ToLongDateString(); } }}
修改一下組件註冊程式,如下代碼所示:
Code 6
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleestatic void Main(string[] args){ Client client = new Client(); CompositionContainer container = new CompositionContainer(); container.AddComponent<Client>(client); container.AddComponent<DateStringProvider>(new DateStringProvider()); container.Bind(); Console.WriteLine(client.OutputTitle);}
輸出結果如所示:
上面的樣本中我們是使用了命名契約,除此之外,還可以使用類型契約,如定義一個字串提供者程式的介面:
Code 7
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleepublic interface IStringProvider{ String OutputTitle { get; set; }}
現在輸出和輸入對應的修改為如下代碼:
Code 8
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleepublic class DateStringProvider : IStringProvider { [Export(typeof(IStringProvider))] public String OutputTitle { get { return DateTime.Now.ToLongDateString(); } }}Code 9
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleepublic class Client{ [Import(typeof(IStringProvider))] public String OutputTitle { get; set; }}
運行後可以看到它與前面的樣本效果是一樣的。
Duck Typing支援
瞭解DI的朋友可能都有這樣的疑問,其實上面的代碼就是一個依賴注入,微軟模式與實踐團隊已經開發出了Unity,為什麼還需要一個MEF呢?其實MEF的定位並不是DI,在前面我已經說過,它主要是用於應用程式擴充管理,下面我們再看一個樣本,它在這方面具有什麼樣的優勢,看下面這段代碼:
Code 10
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terrylee[ContractType("TerryLeeCalculatro")]public interface ICalculator{ int Execute(int x, int y);}public class Client{ [Import(typeof(ICalculator))] public ICalculator Calculator { get; set; }}
這裡我們定義了一個輸入,它裡面具有一個ICalculator的屬性,也就是說它需要的輸入是一個類型是ICalculator的執行個體。現在我們定義輸出,如下代碼所示:
Code 11
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terrylee[Export(typeof(ICalculator))]public class SubCalculator : ICalculator{ public int Execute(int x, int y) { return x - y; }}
在主函數中進行調用:
Code 12
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleestatic void Main(string[] args){ Client client = new Client(); CompositionContainer container = new CompositionContainer(); container.AddComponent<Client>(client); container.AddComponent<SubCalculator>(new SubCalculator()); container.Bind(); Console.WriteLine(client.Calculator.Execute(1,2));}
輸出結果如所示:
現在我們需要對該程式擴充,讓其計算結果為兩個數相加,如果使用DI,我們可能會想到,再編寫一個支援加法計算的類,讓其實現ICalculator介面,然而這裡我們重新定義了一個新的介面IMyCalculator:
Code 13
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terrylee[ContractType("TerryLeeCalculatro")]public interface IMyCalculator{ int Execute(int x, int y);}[Export(typeof(IMyCalculator))]public class AddCalculator : IMyCalculator{ public int Execute(int x, int y) { return x + y; }}
這裡重新定義了一個新介面IMyCalculator,我們為它設定的契約類型和前面定義的介面ICalculator一致。而AddCalculator實現這個介面,同樣用Export標識它為一個輸出。最後調用程式如下:
Code 14
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleestatic void Main(string[] args){ Client client = new Client(); CompositionContainer container = new CompositionContainer(); container.AddComponent<Client>(client); container.AddComponent<AddCalculator>(new AddCalculator()); container.Bind(); Console.WriteLine(client.Calculator.Execute(1,2));}
輸出結果如所示:
大家可能已經意識到了,上面樣本中的輸入需要ICalculator類型,而我們擴充的輸出卻是IMyCalculator類型,它僅僅是與ICalculator標識為相同的契約類型,這種方式帶來了極大的靈活性,也就是說我們在對原有應用程式進行擴充時,並不需要與原有應用程式產生任何依賴,可以獨立的進行擴充。
Plug-In支援
在前面的例子中,始終有一個問題沒有解決,就是當每次編寫一個擴充程式後,都需要修改代碼向CompositionContainer中註冊組件,這樣其實並沒有實現真正的擴充,我們希望的擴充是Plug-In機制。在MEF對於Plug-In提供了很好的支援,它提供了DirectoryWatchingComponentCatalog類來對指定的目錄進行監視,就是說我們定義好了輸入之後,只要把相關的輸出組件放在指定目錄中,MEF會通過反射來進行自動尋找,如我們定義這樣的一個輸入:
Code 15
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleepublic class User{ [Import("Role")] public String Role { get; set; }}
現在定義輸出,我們把它放在一個單獨的類庫項目中:
Code 16
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleepublic class DatabaseProvider{ [Export("Role")] public String AvailableRole { get { return "Developer"; } }}
在主調用程式的目錄下,我們建立一個Extensions的目錄,然後把相關的向外延展群組件都放在該目錄下,並在主調用程式中,為DirectoryWatchingComponentCatalog執行個體加入Extensions目錄,這樣就避免了與具體的擴充應用程式產生依賴,如下代碼所示:
Code 17
// Author: TerryLee 2008.8.30// URL: http://www.cnblogs.com/Terryleestatic void Main(string[] args){ User user = new User(); DirectoryWatchingComponentCatalog catalog = new DirectoryWatchingComponentCatalog(); catalog.AddDirectory(@"Extensions"); CompositionContainer container = new CompositionContainer(catalog.Resolver); container.AddComponent<User>(user); container.Bind(); Console.WriteLine(user.Role); Console.ReadLine();}
運行後輸出結果如下:
對於MEF來說,Duck Typing支援以及Plug-In支援才是它的優勢所在,它不是一個簡單的DI容器,而是一個真正的管理擴充架構。當然了現在MEF還處於CTP階段,很多功能還不是很完善。在8月初,微軟還特意請到了Castle之父Hammett加入該項目組,擔任Program Manager,MEF的未來值得期待,更值得期待的是MEF將會為Silverlight應用程式開發一個MEF子集,讓我們對於Silverlight程式也能夠方便的進行擴充。
Managed Extensibility Framework的官方首頁是:http://code.msdn.microsoft.com/mef。
總結
本文簡單介紹了Managed Extensibility Framework的一些使用,希望對大家有所協助。