MEF中組合部件(Composable Parts)與契約(Contracts)的基本應用
按照MEF的約定,任何一個類或者是接口的實現都可以通過 [System.ComponentModel.Composition.Export] 屬性將其他定義組合部件 (Composable Parts),在任何需要導入組合部件的地方都可以通過在特定的組合部件對象屬性上使用 [System.ComponentModel.Composition.Import ]實現部件的組合,兩者之間通過契約(Contracts)進行通信,實際上這一步可以簡單的理 解為“依賴注入”,本質上就是對象的實例初始化過程。
我個人理解,凡是通過MEF的[ExportAttribute]標注的對象都可以理解為一個可進行組合的部件,包括對象和對象的屬性、字段、方法 、事件等;且該對象可以通過[ImportAttribute]進行導入。如下示例代碼:
public class StringProvider
{
/// <summary>
/// 定義導出部件--契約為“Message”
/// </summary>
[Export("Message")]
public string Output
{
get { return "Hello World"; }
}
}
public class Client
{
/// <summary>
/// 導入指定契約的部件
/// </summary>
[Import("Message")]
public string Input { get; set; }
public void Print()
{
Console.WriteLine(Input);
}
}
所謂的契約也就是一種約定,或者叫做一種規則。如上代碼中就使用到了契約,在對象StringProvider中就定義了一個導出部件屬性( Output),並為其指定了通信契約為“Message”。這裡的“Message”就是一種約定,既約定為在需要使用到這個屬性的地方,都可以通 過 [ImportAttribute]使用契約(Message)進行部件的導入。
接下來結合《Silverlight中使用 CompositionInitializer宿主MEF》一文中使用的日志記錄的應用實例為基礎來看看關於契約 (Contracts)在較為復雜的部件中的的具體使用方法。假設定義了如下的接口與部件實現代碼:
public interface ILogger
{
void WriteLog(string message);
}
[Export(typeof(ILogger))]
public class TXTLogger : ILogger
{
public void WriteLog(string message)
{
MessageBox.Show("TXTLogger>>>>>" + message);
}
}
[Export(typeof(ILogger))]
public class DBLogger : ILogger
{
public void WriteLog(string message)
{
MessageBox.Show("DBLogger>>>>>" + message);
}
}
對於熟悉面向對象設計方法的人一眼就能明白,上面代碼演示了一個接口具有多個實現的場景,仔細觀察會發現在每個實現類上面都添 加了 [ExportAttribute]將其標注為導出部件,並為其添加了通信契約,而且兩個實現類的通信契約都是使用的接口(ILogger)的類型參數 。
這裡需要注意的是在進行導入的時候如果辨別到底是使用的哪一個實現呢?在MEF中提供了一個專門用於導入多個實現的特性 [System.ComponentModel.Composition.ImportManyAttribute],如上的日志實現示例就可以通過如下的方式實現多部件導入。
[ImportMany]
public IEnumerable<ILogger> Loggers { get; set; }
ImportManyAttribute特性可以將實現接口的所有實現全部組合起來。下面為使用[ImportMany]的完整示例代碼:
namespace MEFTraining.CPC
{
public partial class MainPage : UserControl
{
[ImportMany]
public IEnumerable<ILogger> Loggers { get; set; }
public MainPage()
{
InitializeComponent();
CompositionInitializer.SatisfyImports(this);
if (Loggers == null)
{
foreach (var logger in Loggers)
{
logger.WriteLog("Hello World");
}
}
}
}
public interface ILogger
{
void WriteLog(string message);
}
[Export(typeof(ILogger))]
public class TXTLogger : ILogger
{
public void WriteLog(string message)
{
MessageBox.Show("TXTLogger>>>>>" + message);
}
}
[Export(typeof(ILogger))]
public class DBLogger : ILogger
{
public void WriteLog(string message)
{
MessageBox.Show("DBLogger>>>>>" + message);
}
}
}
上面介紹了如何在相同的契約下獲取所有導出部件的實例,在某種情況下或許我們就只直接指導需要使用那一種那個實現方式,那麼是 否可以通過直接指定一個“契約名”就可以從多個實現中獲取到指定的組合部件呢?答案是肯定的,接下來先看看在MEF中中對 ExportAttribute和 ImportAttribute的定義,源代碼如下:
public class ExportAttribute : Attribute
{
public ExportAttribute() : this((string)null, (Type)null){}
public ExportAttribute(Type contractType) : this((string)null, contractType){}
public ExportAttribute(string contractName) : this(contractName, (Type)null) { }
public ExportAttribute(string contractName, Type contractType)
{
this.ContractName = contractName;
this.ContractType = contractType;
}
public string ContractName { get; private set; }
public Type ContractType { get; private set; }
}
ImportAttribute同ExportAttribute一樣提供了相同的重載構造函數,在將一個對象進行導出部件處理的時候可以直接通過 ImportAttribute的屬性給對象指定一個契約名,如本篇前面的日志組件的實現就可以修改為如下代碼格式。
public interface ILogger
{
void WriteLog(string message);
}
[Export("TXT", typeof(ILogger))]
public class TXTLogger : ILogger
{
public void WriteLog(string message)
{
MessageBox.Show("TXTLogger>>>>>" + message);
}
}
[Export("DB", typeof(ILogger))]
public class DBLogger : ILogger
{
public void WriteLog(string message)
{
MessageBox.Show("DBLogger>>>>>" + message);
}
}
通過為不同的導出部件指定了特定的契約名稱,那麼在裝配部件的時候就可以通過契約名進行指定部件的裝配並組合部件,為了方便調 用可以提供一個服務類,將不同的實現通過不同的契約名裝載組合起來以對系統提供一個統一的調用入口。以下為完整的示例代碼:
public partial class MainPage : UserControl
{
/// <summary>
/// 導入日志服務對象
/// </summary>
[Import]
public LogService Service { get; set; }
public MainPage()
{
InitializeComponent();
CompositionInitializer.SatisfyImports(this);
Service.DBLogger.WriteLog("Hello MEF");
Service.TXTLogger.WriteLog("Hello MEF");
}
}
/// <summary>
/// 聚合不同的日志記錄部件,通過MEF進行組合
/// </summary>
[Export]
public class LogService
{
/// <summary>
/// 根據契約名進行部件的裝配
/// </summary>
[Import("TXT")]
public ILogger TXTLogger { get; set; }
[Import("DB")]
public ILogger DBLogger { get; set; }
}
public interface ILogger
{
void WriteLog(string message);
}
[Export("TXT", typeof(ILogger))]
public class TXTLogger : ILogger
{
public void WriteLog(string message)
{
MessageBox.Show("TXTLogger>>>>>" + message);
}
}
[Export("DB", typeof(ILogger))]
public class DBLogger : ILogger
{
public void WriteLog(string message)
{
MessageBox.Show("DBLogger>>>>>" + message);
}
}
本文配套源碼