概述
Managed Extensibility Framework(MEF)是.NET平台下的一個擴展性管理框架,它是一系列特性的集合,包括依賴注入(DI)以及Duck Typing等。MEF為開發人員提供了一個工具,讓我們可以輕松的對應用程序進行擴展並且對已有的代碼產生最小的影響,開發人員在開發過程中根據功能要求定義一些擴展點,之後擴展人員就可以使用這些擴展點與應用程序交互;同時MEF讓應用程序與擴展程序之間不產生直接的依賴,這樣也允許在多個具有同樣的擴展需求之間共享擴展程序。
簡單依賴注入
大家可以去這裡http://code.msdn.microsoft.com/mef下載MEF的CTP版本,在下載包裡有一些簡單的文檔和示例。下面先來看一個簡單的示例,這裡輸出一個字符串:
Code 1
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
static 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/Terrylee
public class WeekStringProvider
{
[Export("Caption")]
public String OutputTitle
{
get { return "星期六"; }
}
}
這裡只是定義了一個簡單的屬性,其實在同一個類型可以定義多個輸出,Export同時指定了一個字符串的契約名稱,這意味著任何匹配契約名稱的程序都可以使用該擴展。除此之外,我們還可以指定一個類型來代替字符串的契約名稱,後面會說到。我們再定義一個輸入,即用來消費該字符串,同樣是一個簡單的屬性,不過這次添加的是Import特性:
Code 3
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public class Client
{
[Import("Caption")]
public String OutputTitle { get; set; }
}
現在有了輸出和輸入,就可以在主程序中進行調用了,需要創建一個CompositionContainer容器,並添加所有的組件到該容器中,再調用它的Bind()方法,一旦調用該方法後,就可以使用所有的組件了,如下代碼所示:
Code 4
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
static 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/Terrylee
public class DateStringProvider
{
[Export("Caption")]
public String OutputTitle
{
get { return DateTime.Now.ToLongDateString(); }
}
}
修改一下組件注冊程序,如下代碼所示:
Code 6
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
static 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/Terrylee
public interface IStringProvider
{
String OutputTitle { get; set; }
}
現在輸出和輸入對應的修改為如下代碼:
Code 8
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public 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/Terrylee
public 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/Terrylee
static 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/Terrylee
static 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/Terrylee
public class User
{
[Import("Role")]
public String Role { get; set; }
}
現在定義輸出,我們把它放在一個單獨的類庫項目中:
Code 16
// Author: TerryLee 2008.8.30
// URL: http://www.cnblogs.com/Terrylee
public 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/Terrylee
static 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。