代码改变世界

我学MEF系列(1):MEF是什么

2011-11-27 13:33  ps_zw  阅读(6237)  评论(3编辑  收藏  举报

前言:

      一种新技术,一个新框架的出现并不是偶然的,它们都是为了解决一定的实际问题。如今IT各种技术、框架的更新很迅速,一步留神就感觉自己out了,当然,技术没有优劣好坏之分,能更好的解决实际问题我们就使用它。MEF也是一样,它能解决很多的现实问题,当然它也有它的局限性。

现实问题:

      在开发大型应用程序,特别是客户端程序,我们会把各个独立的模块/功能包装成一个个小的组件,程序运行时按需加载。这样,我们就要求程序具有很好的扩展能力,包括但不限于以下列出的几个方面:

1. 按需加载组件 ,动态可拆卸(可动态发现、重用和组合); 

2. 能够在已投产的产品中灵活的加入新功能;

3. 能够支持第三方扩展。

如何解决问题:

  发现问题了便要想办法去解决这些问题。通常为了解决扩展问题,我们都会在框架中设置一些扩展点(或者接口),用于组件(扩展者)的扩展。程序运行时按需(或者按配置)将这些扩展点和扩展者加载并关联。

目前能够满足需求的框架有很多,如:Castle,Unity等,他们都有各自的优缺点,如castle通过在配置文件中显式注册可用组件来实现的,配置文件的劣势在于配置出错后需要很长的时间来解决问题。MSDN的描述如下:

由于应用程序缺乏自己发现组件的能力,因此仍必须明确告知应用程序哪些组件可用并应加载。 这通常是通过在一个配置文件中显式注册可用组件来实现的。 这意味着,确保组件正确无误成为了一个日常维护问题,尤其是在执行更新操作的是最终用户而非开发人员的情况下。

MEF是什么:

MEF,全称Managed Extensibility Framework(托管可扩展框架)。单从名字我们不难发现:MEF是专门致力于解决扩展性问题的框架,MSDN中对MEF有这样一段说明:

Managed Extensibility Framework 或 MEF 是一个用于创建可扩展的轻型应用程序的库。 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置。 扩展开发人员还可以利用该库轻松地封装代码,避免生成脆弱的硬依赖项。 通过 MEF,不仅可以在应用程序内重用扩展,还可以在应用程序之间重用扩展。

为什么选择MEF:

  MEF不是第一种解决扩展问题的框架,但我们之所以使用它还是在于MEF的一些独有的特点,MSDN杂志有一篇文章特别说明了为什么微软还要开发一套可扩展框架,

文章地址:http://msdn.microsoft.com/zh-cn/magazine/ee291628.aspx

针对开发MEF的描述如下:

无论如何,MEF 不是此问题的第一种解决方案。人们提出过许多解决方案 — 跨越平台边界的尝试数不胜数,涉及的工作包括 EJB、CORBA、Eclipse 的 OSGI 实现以及 Java 端的 Spring 等等。在 Microsoft 的平台上,.NET Framework 自身内部包含组件模型和 System.Addin。同时存在若干种开源解决方案,包括 SharpDevelop 的 SODA 体系结构和“控制反转”容器(如 Castle Windsor、Structure Map 以及模式和实践的 Unity)。 既然目前已有这些方法,为何还要引入新事物?这是因为我们意识到,我们当前的所有解决方案对于常规第三方可扩展性都不理想。这些解决方案要么规模过大,不适合常规用途,要么需要主机或扩展开发人员一方完成过多工作。MEF 在最大程度上秉承了所有这些解决方案的优点,尝试解决刚才所提及的令人头痛的问题。

通过上面两段MSDN中的说明,我个人对为什么选择MEF总结如下:

1.它解决了扩展性的问题(这是第一位的,不然我们就没必要介绍MEF了);

2.它轻量级,使用时只需要引用一个dll库;

3.扩展性不是通过配置文件实现,而是使用特性化编程模型

4.该框架是开源的,可在codeplex上下载源码;

5.它是微软开发的(这一点很重要)。

“MEF 是 .NET Framework 4 的组成部分,可用在任何使用 .NET Framework 的地方。 可以在客户端应用程序中使用 MEF(无论应用程序使用的是 Windows 窗体、WPF,还是任何其他技术),也可以在使用 ASP.NET 的服务器应用程序中使用 MEF”      ——MSDN

MEF的工作原理:

  MEF不同于显式注册可用组件的做法,在MEF中组件被视为部件。这些部件主要有“导入”(Import)部件和“导出”(Export)部件,此外MEF提供了一个组合容器(ComposeContainer),容器会发现指定Catalog的导入导出部件,并按Contract(协定)、Metadata(元数据)等对导入和导出部件进行组合。

通过 MEF,应用程序可以通过部件的元数据来发现并检查部件,而不用实例化部件,或者甚至不用加载部件的程序集。 因此,没有必要仔细指定应何时以及如何加载扩展。 使用 MEF 编写的可扩展应用程序会声明一个可由扩展组件填充的导入,而且还可能会声明导出,以便向扩展公开应用程序服务。 每个扩展组件都会声明一个导出,而且还可能会声明导入。 通过这种方式,扩展组件本身是自动可扩展的。

上述文字已经把MEF中涉及的相关概念指出了,下面具体说明一下:

Export(导出): “Export”也就是我们常说的组件或者模块或者服务,它是部件向容器中的其他部件提供的一个值、功能或服务等;

Import(导入): "Import”,既扩展点,是组件,服务等接入系统的窗口是部件向要通过可用导出满足的容器提出的要求,MEF 支持若干导入类型,其中包括动态导入、延迟导入、必备导入和可选导入

Contract(协定):是Export和Import的一种约定,一种协议,只有Contract相匹配的Import和Export部件才能组装成功;

Catalog(目录):为了发现可用于组合容器的部件,组合容器将使用“Catalog”。 目录是一个对象,通过它发现可用部件, MEF 提供了用于从提供的类型、程序集或磁盘路径创建Catalog。

Compose(组合):在MEF中,容器将导入与导出匹配的这一过程我们称之为组合,部件由 MEF 组合,MEF 将部件实例化,然后使导出程序与导入程序相匹配。

示例

  本示例来源于MSDN:

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace SimpleCalculator3
{

public interface ICalculator
{
String Calculate(String input);
}

public interface IOperation
{
int Operate(int left, int right);
}

public interface IOperationData
{
Char Symbol { get; }
}

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
class Add : IOperation
{
public int Operate(int left, int right)
{
return left + right;
}
}

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '-')]
class Subtract : IOperation
{

public int Operate(int left, int right)
{
return left - right;
}

}



[Export(typeof(ICalculator))]
class MySimpleCalculator : ICalculator
{
[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;

public String Calculate(String input)
{
int left;
int right;
Char operation;
int fn = FindFirstNonDigit(input); //finds the operator
if (fn < 0) return "Could not parse command.";

try
{
//separate out the operands
left = int.Parse(input.Substring(0, fn));
right = int.Parse(input.Substring(fn + 1));
}
catch
{
return "Could not parse command.";
}

operation = input[fn];

foreach (Lazy<IOperation, IOperationData> i in operations)
{
if (i.Metadata.Symbol.Equals(operation)) return i.Value.Operate(left, right).ToString();
}
return "Operation Not Found!";
}

private int FindFirstNonDigit(String s)
{

for (int i = 0; i < s.Length; i++)
{
if (!(Char.IsDigit(s[i]))) return i;
}
return -1;
}


}


class Program
{
private CompositionContainer _container;

[Import(typeof(ICalculator))]
public ICalculator calculator;


private Program()
{
//An aggregate catalog that combines multiple catalogs
var catalog = new AggregateCatalog();
//Adds all the parts found in the same assembly as the Program class
catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
catalog.Catalogs.Add(new DirectoryCatalog("C:\\Users\\SomeUser\\Documents\\Visual Studio 2010\\Projects\\SimpleCalculator3\\SimpleCalculator3\\Extensions"));


//Create the CompositionContainer with the parts in the catalog
_container = new CompositionContainer(catalog);

//Fill the imports of this object
try
{
this._container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
Console.WriteLine(compositionException.ToString());
}
}


static void Main(string[] args)
{
Program p = new Program(); //Composition is performed in the constructor
String s;
Console.WriteLine("Enter Command:");
while (true)
{
s = Console.ReadLine();
Console.WriteLine(p.calculator.Calculate(s));
}


}
}
}

本文由于时间仓促没有自己写示例,以后补上。