學習MEF系列(1):MEF是什麼?

前言:

      一種新技術,一個新框架的出現並不是偶然的,它們都是爲了解決一定的實際問題。如今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:

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

        }
    }
}

本文由於時間倉促沒有自己寫示例,以後補上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章