MEF程序設計指南三: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 { getset; }

    
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 { getset; }

 

     

 

   ImportManyAttribute特性可以將實現接口的所有實現全部組合起來。下面爲使用[ImportMany]的完整示例代碼:

namespace MEFTraining.CPC
{
    
public partial class MainPage : UserControl
    {
        [ImportMany]
        
public IEnumerable<ILogger> Loggers { getset; }

        
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 { getprivate set; }
    
public Type ContractType { getprivate 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 { getset; }

    
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 { getset; }

    [Import(
"DB")]
    
public ILogger DBLogger { getset; }
}

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);
    }
}

 

   注:本文參考於Defining Parts and Contracts,點擊鏈接可訪問英文原文。

  MEF官方網站:http://mef.codeplex.com/

  推薦資源:在應用程序中宿主MEF

        Silverlight中使用CompositionInitializer宿主MEF

  

發佈了110 篇原創文章 · 獲贊 3 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章