【转】MEF程序设计指南三:MEF中组合部件(Composable Parts)与契约(Contracts)的基本应用

时间:2023-02-19 19:39:18

  按照MEF的约定,任何一个类或者是接口的实现都可以通过[System.ComponentModel.Composition.ExportAttribute] 特性将其定义为组合部件(Composable Parts),在任何需要导入组合部件的地方都可以通过在特定的组合部件对象属性上使用[System.ComponentModel.Composition.ImportAttribute ]实现组合部件的导入,两者之间通过契约(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; }

 

     【转】MEF程序设计指南三:MEF中组合部件(Composable Parts)与契约(Contracts)的基本应用

 

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