前言
最近在幫一家知名外企開發(fā)Universal Windows Platform的相關(guān)應(yīng)用辽剧,開發(fā)過程中不由感慨:項目分為兩種,一種叫做前人栽樹后人乘涼,一種叫做前人挖坑后人遭殃。不多說了莽囤,多說又要變成月經(jīng)貼了。
講講MEF切距。
MEF全稱Managed Extensibility Framework朽缎。我們做.Net的碰到依賴注入(DI:Dependency Injection)這一塊的內(nèi)容,一般會選擇使用Unity或者MEF谜悟,這也是Prism主要使用的兩種方式话肖。在.Net 4.0之前,MEF一直作為擴展的形式存在葡幸,但是.Net 4.0的時候最筒,已經(jīng)作為Framework的一部分了。但是.Net 4.0的MEF只是原始的版本礼患,后面MEF 2又加入了泛型類導(dǎo)入導(dǎo)出等等特性是钥。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的時候厨相,一般都會去描述這是一個用來實行插件式開發(fā)的一套東西领曼。當插件式開發(fā)成為了一種可能鸥鹉,那就意味著我們的項目可以被完整的解耦,這就保證了我們程序的健壯性庶骄,同時在開發(fā)的過程中我們也避免了各種開發(fā)人員的沖突毁渗。
開始
怎樣開始寫一個基于MEF的程序?
假設(shè)我們現(xiàn)在寫的是一個UWP的項目单刁,并且我們采用C#+XAML的方式灸异。因為MVVM是XAML的主打的方式,可以很好的應(yīng)用綁定數(shù)據(jù)的這個模型羔飞,所以我們采用MVVM肺樟。
所以我們決定設(shè)計一個基于C#+XAML的通過MVVM模式來進行View和ViewModel的解耦,中間我們也可以實現(xiàn)一個觀察者模式的消息傳遞方式來進行View之間的相互傳參等等逻淌∶床看是確實很完美,可以很輕易的下載一個Mvvmlight來直接用卡儒。
我們看樣子已經(jīng)決定了View層和ViewModel層的問題田柔,那么對于一個完整的系統(tǒng),我們還缺少一些什么組件呢朋贬?我們可以還缺少數(shù)據(jù)凯楔,所以我們需要數(shù)據(jù)層,一般我們命名為Service層锦募,來進行跟數(shù)據(jù)庫或者服務(wù)器的通信摆屯;還缺什么呢?日志系統(tǒng)糠亩,我們需要進行運行過程中的一些數(shù)據(jù)統(tǒng)計虐骑,或者異常捕獲后的消息記錄,所以我們需要Log層赎线;還需要緩存層廷没,緩存我們的數(shù)據(jù)到內(nèi)存或者磁盤,這樣看上去我們的程序能夠運行的稍微好一點垂寥。
當我們決定了以后颠黎,我們現(xiàn)在需要寫的東西如下:
- 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類狭归,然后各層作為只讀靜態(tài)屬性來提供各類功能,看上去不錯文判,我們也能很好的調(diào)用过椎。
但是有個問題,當這個類出現(xiàn)的時候戏仓,我們不希望Service等等類再被外部實例化疚宇。很不幸亡鼠,當Service等作為一個可以Public的屬性時,這個類本身為了訪問的一致性就也要不可以避免的被標記為Public敷待,這破壞了我們設(shè)立這個類的初衷间涵。
那我們怎么繼續(xù)解決這個問題?把Service等類都設(shè)計為單例榜揖。這也是一個解決方案浑厚。但無論是這種解決方案還是代碼里的解決方案,都強引用的意味都太強了根盒,稍加不慎钳幅,系統(tǒng)就會崩潰。
我們不希望引入Hub炎滞,也不想Service等類被設(shè)計為單例敢艰,同時具體的ViewModel中也不希望出現(xiàn)具體的Service的實例,那我們應(yīng)該怎么辦册赛?
答案:依賴注入钠导。
重新設(shè)計
保留我們之前所設(shè)想的所有的組件,引入接口來進入注入:
- IService
- ILog
- ICache
看一下我們的ViewModel現(xiàn)在應(yīng)該是怎么樣的?
public class ViewModel
{
ILog Log;
IService Service;
ICache Cache;
public ViewModel(ILog log, IService service, ICache cache)
{
Log = log;
Service = service;
Cache = cache;
}
}
又進了一步森瘪,我們只需要調(diào)用的時候給我們需要的實例就行了牡属。如果我們需要View,我們還能聲明一個IView的接口扼睬。
至此逮栅,我們設(shè)計還沒有引入MEF,看上去已經(jīng)相對比較好的解耦了窗宇,我們只有在調(diào)用ViewModel的時候措伐,引入具體的是實例,耦合發(fā)生在了此處军俊。
引入MEF
試想一下侥加,既然我們需要生成的實例的對象都已經(jīng)在我們的DLL之中,為什么我們還要手動的去生成一個實例粪躬,然后再傳到具體的構(gòu)造函數(shù)里面担败,它就不能自己尋找嗎?
假設(shè)我們的類都有一個別名,然后我們在需要引用的地方告訴告訴程序镰官,我們需要一個實現(xiàn)Ixxx接口的類提前,它的名字叫做xxx,這樣我們是不是更進了一部朋魔。如下:
public class ViewModel
{
[Import("LogSample")]
ILog Log;
[Import("ServiceSample")]
IService Service;
[Import("CacheSample")]
ICache Cache;
public ViewModel()
{
}
}
當我們構(gòu)造函數(shù)完成后岖研,Log等對象就已經(jīng)自動在程序集中找到名為LogSample的的實現(xiàn)ILog的類卿操,Service也是警检,Cache也是孙援。
看下Log
[Export("LogSample", typeof(ILog))]
public class Log : ILog
{
}
到現(xiàn)在為止,我們主要關(guān)注具體的功能實現(xiàn)就好了扇雕。
MEF正式引入
為了簡化我們的程序拓售,更加關(guān)注MEF的本質(zhì),我們把程序設(shè)計為僅包括下列的組件
- View
- ViewModel
- Service
建立我們的項目如下
代碼已經(jīng)完整的托管到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;
}
}
將所有的程序集加入容器之中,然后通過容器去創(chuàng)建對象哨苛。
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;
}
}
演示
初始的狀態(tài):
當我們點擊Show View 1按鈕時鸽凶,容器去創(chuàng)建View1的實例,View1所需要的實例建峭,又會根據(jù)導(dǎo)入導(dǎo)出的原則去創(chuàng)建玻侥。創(chuàng)建完成后,
點擊View 1 Click后亿蒸,會將ViewModel層的數(shù)據(jù)傳給Service凑兰,Service又調(diào)用回掉函數(shù),將數(shù)據(jù)放置到UI上边锁。
也可以點擊Show View 2進行相應(yīng)的操作姑食。
總結(jié)
本文講述了一個簡單的MEF在UWP下的引用,體現(xiàn)了MEF通過依賴注入的方式將程序更好的解耦茅坛。閱讀本文希望對你有所幫助音半。
謝謝~
代碼下載:http://files.cnblogs.com/files/youngytj/uwp_MEF.zip
參考資料
Unity
《MEF程序設(shè)計指南》博文匯總
Prism
Prism與MVVM、Unity贡蓖、MEF關(guān)系
依賴注入那些事兒
System.Composition