前言最近在幫一家知名外企開發Universal Windows Platform的相關應用,開發過程中不由感慨:項目分為兩種,一種叫做前人栽樹後人乘涼,一種叫做前人挖坑後人遭殃。不多說了,多說又要變成月經貼了。講講MEF。MEF全稱Managed Extensibility Framework。我們...
- 前言
最近在幫一家知名外企開發Universal Windows Platform的相關應用,開發過程中不由感慨:項目分為兩種,一種叫做前人栽樹後人乘涼,一種叫做前人挖坑後人遭殃。不多說了,多說又要變成月經貼了。
講講MEF。
MEF全稱Managed Extensibility Framework。我們做.Net的碰到依賴註入(DI:Dependency Injection)這一塊的內容,一般會選擇使用Unity或者MEF,這也是Prism主要使用的兩種方式。在.Net 4.0之前,MEF一直作為擴展的形式存在,但是.Net 4.0的時候,已經作為Framework的一部分了。但是.Net 4.0的MEF只是原始的版本,後面MEF 2又加入了泛型類導入導出等等特性。MEF 2不作為.Net的一部分,又變成了以擴展包的形式存在,支持了包括.Net 4.5以及之後的平臺,我們可以通過Nuget獲取這個擴展,源碼也被托管在了codeplex平臺。
MEF2支持的平臺
- .NET Framework 4.5
- Windows 8
- Windows Phone 8.1
- Windows Phone Silverlight 8
- Portable Class Libraries
通常意義上,當我們講到MEF的時候,一般都會去描述這是一個用來實行插件式開發的一套東西。當插件式開發成為了一種可能,那就意味著我們的項目可以被完整的解耦,這就保證了我們程式的健壯性,同時在開發的過程中我們也避免了各種開發人員的衝突。
- 開始
怎樣開始寫一個基於MEF的程式?
假設我們現在寫的是一個UWP的項目,並且我們採用C#+XAML的方式。因為MVVM是XAML的主打的方式,可以很好的應用綁定數據的這個模型,所以我們採用MVVM。
所以我們決定設計一個基於C#+XAML的通過MVVM模式來進行View和ViewModel的解耦,中間我們也可以實現一個觀察者模式的消息傳遞方式來進行View之間的相互傳參等等。看是確實很完美,可以很輕易的下載一個Mvvmlight來直接用。
我們看樣子已經決定了View層和ViewModel層的問題,那麼對於一個完整的系統,我們還缺少一些什麼組件呢?我們可以還缺少數據,所以我們需要數據層,一般我們命名為Service層,來進行跟資料庫或者伺服器的通信;還缺什麼呢?日誌系統,我們需要進行運行過程中的一些數據統計,或者異常捕獲後的消息記錄,所以我們需要Log層;還需要緩存層,緩存我們的數據到記憶體或者磁碟,這樣看上去我們的程式能夠運行的稍微好一點。
當我們決定了以後,我們現在需要寫的東西如下:
- View
- ViewModel
- Service
- Log
- Cache
想想我們怎麼處理這個問題呢?
public sealed class Hub { public static Log Log { get; private set; } public static Service Service { get; private set; } public static Cache Cache { get; private set; } private static Hub instance = null; private static readonly object padlock = new object(); Hub() { Log = new Log(); Service = new Service(); Cache = new Cache(); } public static Hub Instance { get { if (instance == null) { lock (padlock) { if (instance == null) { instance = new Hub(); } } } return instance; } } }
上面的解決方案,引入一個單例的Hub類,然後各層作為只讀靜態屬性來提供各類功能,看上去不錯,我們也能很好的調用。
但是有個問題,當這個類出現的時候,我們不希望Service等等類再被外部實例化。很不幸,當Service等作為一個可以Public的屬性時,這個類本身為了訪問的一致性就也要不可以避免的被標記為Public,這破壞了我們設立這個類的初衷。
那我們怎麼繼續解決這個問題?把Service等類都設計為單例。這也是一個解決方案。但無論是這種解決方案還是代碼里的解決方案,都強引用的意味都太強了,稍加不慎,系統就會崩潰。
我們不希望引入Hub,也不想Service等類被設計為單例,同時具體的ViewModel中也不希望出現具體的Service的實例,那我們應該怎麼辦?
答案:依賴註入。
- 重新設計
保留我們之前所設想的所有的組件,引入介面來進入註入:
- IService
- ILog
- ICache
看一下我們的ViewModel現在應該是怎麼樣的?
public class ViewModel { ILog Log; IService Service; ICache Cache; public ViewModel(ILog log, IService service, ICache cache) { Log = log; Service = service; Cache = cache; } }
又進了一步,我們只需要調用的時候給我們需要的實例就行了。如果我們需要View,我們還能聲明一個IView的介面。
至此,我們設計還沒有引入MEF,看上去已經相對比較好的解耦了,我們只有在調用ViewModel的時候,引入具體的是實例,耦合發生在了此處。
- 引入MEF
試想一下,既然我們需要生成的實例的對象都已經在我們的DLL之中,為什麼我們還要手動的去生成一個實例,然後再傳到具體的構造函數裡面,它就不能自己尋找嗎?
假設我們的類都有一個別名,然後我們在需要引用的地方告訴告訴程式,我們需要一個實現Ixxx介面的類,它的名字叫做xxx,這樣我們是不是更進了一部。如下:
public class ViewModel { [Import("LogSample")] ILog Log; [Import("ServiceSample")] IService Service; [Import("CacheSample")] ICache Cache; public ViewModel() { } }
當我們構造函數完成後,Log等對象就已經自動在程式集中找到名為LogSample的的實現ILog的類,Service也是,Cache也是。
看下Log
[Export("LogSample", typeof(ILog))] public class Log : ILog { }
到現在為止,我們主要關註具體的功能實現就好了。
- MEF正式引入
為了簡化我們的程式,更加關註MEF的本質,我們把程式設計為僅包括下列的組件
- View
- ViewModel
- Service
建立我們的項目如下
代碼已經完整的托管到GitHub上,可以方便的查閱。
我們在Service中寫了一個演示的功能:
[Export(Constant.Confing.SampleService,typeof(IService))] public class SampleService : IService { public void QueryData(int numuber, Action<int> action) { action(numuber); } }
我們看一下我們的程式的主界面:
public sealed partial class MainPage : Page { [Import(Constant.Confing.View1)] public IView View1 { get; set; } [Import(Constant.Confing.View2)] public IView View2 { get; set; } public MainPage() { this.InitializeComponent(); this.Loaded += MainPage_Loaded; } private CompositionHost host; private void MainPage_Loaded(object sender, RoutedEventArgs e) { List<Assembly> assemblies = new List<Assembly>() { Assembly.Load(new AssemblyName("MEF.Service")), Assembly.Load(new AssemblyName("MEF.View")), Assembly.Load(new AssemblyName("MEF.ViewModel")), Assembly.Load(new AssemblyName("MEF.Abstract")) }; ContainerConfiguration configuration = new ContainerConfiguration().WithAssemblies(assemblies); host = configuration.CreateContainer(); host.SatisfyImports(this); } private void View1_Click(object sender, RoutedEventArgs e) { IView view = host.GetExport(typeof(IView), Constant.Confing.View1) as IView; frame.Content = view; } private void View2_Click(object sender, RoutedEventArgs e) { IView view = host.GetExport(typeof(IView), Constant.Confing.View2) as IView; frame.Content = view; } }
將所有的程式集加入容器之中,然後通過容器去創建對象。
View的代碼:
[Export(Constant.Confing.View1,typeof(IView))] public sealed partial class View1 : UserControl, IView { IService Service; IViewModel ViewModel; [ImportingConstructor] public View1( [Import(Constant.Confing.SampleService)]IService service, [Import(Constant.Confing.ViewModel1)]IViewModel viewModel) { this.InitializeComponent(); this.Service = service; this.ViewModel = viewModel; } private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) { Service.QueryData(ViewModel.Number, ShowValue); } private void ShowValue(int i) { Btn.Content = i; } }
- 演示
初始的狀態:
當我們點擊Show View 1按鈕時,容器去創建View1的實例,View1所需要的實例,又會根據導入導出的原則去創建。創建完成後,
點擊View 1 Click後,會將ViewModel層的數據傳給Service,Service又調用回掉函數,將數據放置到UI上。
也可以點擊Show View 2進行相應的操作。
- 總結
本文講述了一個簡單的MEF在UWP下的引用,體現了MEF通過依賴註入的方式將程式更好的解耦。閱讀本文希望對你有所幫助。
謝謝~
代碼下載:http://files.cnblogs.com/files/youngytj/uwp_MEF.zip
- 參考資料: