zoukankan      html  css  js  c++  java
  • MEF 打造的插件系统

    以实例说话,一起体验MEF带来的可扩展性吧,Let’s Rock!!!

    1:新建控制台程序SimpleCalculator

    在这里要实现的程序时SimpleCalculator,顾名思义:简单的计算器。

    所以我们需要定义一个用来计算的接口:

    public interface ICalculator

    {

        String Calculate(String input);

    }

    Program 的代码如下:

    class Program 

        private CompositionContainer _container; 

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

        public Program() 
        { 
            //var catalog = new AggregateCatalog(); 
            //catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly)); 

            var catalog = new AssemblyCatalog(typeof(Program).Assembly); 
            _container = new CompositionContainer(catalog); 

            try 
            { 
                this._container.ComposeParts(this); 
            } 
            catch (CompositionException compositionException) 
            { 
                Console.WriteLine(compositionException.ToString()); 
            } 
        } 

        static void Main(string[] args) 
        { 
            Program p = new Program(); 
            string s; 
            Console.WriteLine("Enter Command:"); 

            while (true) 
            { 
                s = Console.ReadLine(); 
                Console.WriteLine(p.calculator.Calculate(s)); 
            } 
        } 
    }

    MEF所要解决的是寻找插件的功能,传统的实现插件的方式主要是使用接口,即声明一个接口,然后使用配置文件来配置接口使用哪个实现类。

    微软知道有这种需求,于是提供了MEF来实现插件的功能。

    Composite 原理

    1:声明一个 CompositionContainer 对象,这个对象里面包含一堆Catalog.

    2:这堆Catalog如果是AssemblyCatalog,则在Assembly中查找,如果是DirectoryCatalog,

    在Directory 中查找,如果即想要在Assembly中查找,又需要在Directory中查找,

    则采用AggregateCatalog。

    3:然后在这堆Catalog中查找与Import 特性相对应的Export标记所标记的实现类,调用实现类的构造函数进行

    Composite(组合)。

    知道原理后,你也可以自己实现自己的CompositionContainer 类了,

    要使用MEF 需要为SimpleCalculator添加 System.ComponentModel.Composition.dll 的引用,

    然后导入命名空间:

    using System.ComponentModel.Composition;

    using System.ComponentModel.Composition.Hosting;

     

    接下来看下Program 的构造函数所做的事情:

    声明一个AssemblyCatalog,指向Program所在的Assembly. 然后把它添加到

    CompositionContainer中,调用CompositionContainer 的ComposeParts 扩展方法,来Compose(this) 的Parts。

    注:ComposeParts 是扩展方法,需要using System.ComponentModel.Composition;

    OK,如何Compose,在哪个Assembly中查找实现类来进行Compose已经完成了。

    目前的问题是:哪些类需要Compose??

    为了回答这个问题,微软提供了Import和Export特性: 

    Import:哪个对象需要Compose。也就是需要被实现类给填充,所以Import标记的是对象,一般该对象是接口,因为如果是具体类的话,那还需要Import吗?

    Export:哪个类可以被用来Compose,也就是说这个类是不是可以用来填充的实现类,所以Export标记的是类,而不是具体的某个对象。

    所以在这里calculator 使用Import 特性来标记:

    [Import(typeof(ICalculator))]

    private ICalculator calculator;

    接下来MEF 的组合引擎在ComposeParts(this)的时候,就会在catalog 代表的AssemblyCatalog中查找Export特性所修饰的实现类了,找到实现类后进行Compose。

    如果找不到Export特性修饰的类的话,结果如下:

    OK,接下来添加一个实现类,并使用Export特性来进行修饰:

    [Export(typeof(ICalculator))]

     public class MySimpleCalculator : ICalculator

     {

         public string Calculate(string input)

         {

             return "MySimpleCalculator 处理了" + input;

         }

     }

     

    运行结果如下:

    当然Import和Export还提供了其他的构造函数,所以你还可以将上面的Import和Export修改为:

    [Import("calculator1", typeof(ICalculator))]

    [Export("calculator1", typeof(ICalculator))]

    之所以提供ContractName为calculator1 是因为你可能有多个ICalculator对象需要填充。

    修改Program的代码如下:

    class Program

    {

        private CompositionContainer _container;

     

        [Import("calculator1", typeof(ICalculator))]

        private ICalculator calculator1;

     

        [Import("calculator2", typeof(ICalculator))]

        private ICalculator calculator2;

     

        public Program()

        {

            //var catalog = new AggregateCatalog();

            //catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

     

            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

            _container = new CompositionContainer(catalog);

     

            try

            {

                this._container.ComposeParts(this);

            }

            catch(CompositionException compositionException)

            {

                Console.WriteLine(compositionException.ToString());

            }

        }

     

        static void Main(string[] args)

        {

            Program p = new Program();

            string s;

            Console.WriteLine("Enter Command:");

     

            while (true)

            {

                s = Console.ReadLine();

                Console.WriteLine(p.calculator1.Calculate(s));

                Console.WriteLine(p.calculator2.Calculate(s));

            }

        }

    }

    修改Export修饰的类为:

    [Export("calculator1", typeof(ICalculator))]

    public class MySimpleCalculator1 : ICalculator

    {

        public string Calculate(string input)

        {

            return "第一个Calculator 处理了" + input;

        }

    }

     

    [Export("calculator2", typeof(ICalculator))]

    public class MySimpleCalculator2 : ICalculator

    {

        public string Calculate(string input)

        {

            return "第二个Calculator 处理了" + input;

        }

    }

     

    运行结果如下:

    因为Import和Export是一一对应的,在现实世界中,存在着大量一对多的情况,微软也预料到了这种情况,所以提供了ImportMany 特性。

    在上个例子中的MySimpleCalculator的Calculate方法返回的是一句话,在这个例子中要真正实现计算的功能,例如输入5+3,输出8,输入7*4,输出28。

    为了支持 + - * / 四种Operation.所以在MySimpleCalculator中声明一个operations 的列表。

    [Export(typeof(ICalculator))]

    class MySimpleCalculator : ICalculator

    {

        [ImportMany]

        IEnumerable<Lazy<IOperation, IOperationData>> operations;

     

        public string Calculate(string input)

            {

    return "calculate 处理了" + input;

            }

    }

     

    之所以在MySimpleCalculator 中声明operations ,是因为是计算器支持多种运算。因为operations 需要多个operation 来Compose(填充),所以使用ImportMany特性来修饰,和Import特性一样,ImportMany特性一般也是修饰接口。

     

    Ioperation 和IOperationData的定义如下:

     

    public interface IOperation

    {

        int Operate(int left, int right);

    }

     

    public interface IOperationData

    {

        Char Symbol { get; }

    }

     

    Lazy<IOperation, IOperationData> operations:

    提供对对象及其关联的元数据的延迟间接引用,以供 Managed Extensibility Framework 使用。

    意思是说IOperation 和IOperationData之间的引用需要延迟,为什么需要延迟?,因为IOperation需要根据IOperationData的Symbol符号来延迟创建。

    也就是说,如果IOperationData的Symbol 等于 “+”,那么IOperation对象是AddOperation.如果IOperationData的Symbol等于”-”,那么IOperation对象是SubtractOperation.

    那么如何保证这点呢?

     

    关键点就在于ExportMetadata attribute 上。

    看下Add Operation 的定义:

     

    [Export(typeof(IOperation))]

    [ExportMetadata("Symbol", '+')]

    class Add : IOperation

    {

        public int Operate(int left, int right)

        {

            return left + right;

        }

    }

     

    在这里ExportMetadata特性的Symbol 为+。所以当IOperationData的Symbol为”+” 的时候,匹配的就是Add Operation

     

    MySimpleCalculator 的完整代码如下:

     

    [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 = FindFirstNonDigitPosition(input);

     

                if (fn < 0) return "Could not parse command.";

     

                try

                {

                    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 FindFirstNonDigitPosition(string s)

        {

            for (int i = 0; i < s.Length; i++)

            {

                if (!(Char.IsDigit(s[i]))) return i;

            }

            return -1;

        }

    }

     

    回头再看看上例的Program代码:

    class Program

    {

        private CompositionContainer _container;

     

        [Import(typeof(ICalculator))]

        private ICalculator calculator;

     

        public Program()

        {

            //var catalog = new AggregateCatalog();

            //catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

     

            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

     

            _container = new CompositionContainer(catalog);

     

            try

            {

                this._container.ComposeParts(this);

            }

            catch(CompositionException compositionException)

            {

                Console.WriteLine(compositionException.ToString());

            }

        }

     

        static void Main(string[] args)

        {

            Program p = new Program();

            string s;

            Console.WriteLine("Enter Command:");

     

            while (true)

            {

                s = Console.ReadLine();

                Console.WriteLine(p.calculator.Calculate(s));

            }

        }

    }

     

     

    当this._container.ComposeParts(this); 的时候,MEF组合引擎就开始对标记了Import特性的接口进行Compose,所以在这里是calculator。在哪里找实现类呢?,AssemblyCatalog表明在Program的当前Assembly中查找实现类,所以找到了MySimpleCalculator在构造MySimpleCalculator 的时候,发现了ImportMany特性修饰的operations。于是继续在AssemblyCatalog中找到了Add。

     

    上面的过程是Compose的过程。

    那么MySimpleCalculator 如何进行Calculate的呢?

    例如5+3

    1:找出第一个非数字的位置,也就是需要找出 +。

    2:声明left,right.并且left 为5,right为3.

    3:根据符号+来构造IOperation对象,接着调用IOperation对象的Operate(left,right)方法。

    foreach (Lazy<IOperation, IOperationData> i in operations)

     {

         if (i.Metadata.Symbol.Equals(operation))

             return i.Value.Operate(left, right).ToString();

     }

     

    运行结果:

    因为目前定义了Add 的Operation。所以根据符号+ 能够找到Add,但是*我们没有定义,所以Operation Not Found!.

     

    于是开始定义Multiple:

    [Export(typeof(IOperation))]

     [ExportMetadata("Symbol", '*')]

     class Multiple : IOperation

     {

         public int Operate(int left, int right)

         {

             return left * right;

         }

     }

     

    再次运行,结果如下:

     

     

    当然还可以在当前程序集下面增加- ,/,^,% 等Operation。

     

    为了让事情更加的有趣,我打算在Debug目录下增加一个目录CalculateExtensions,然后将-,/ ..的Operation放到里面来,让MEF自动发现。

    首先新建类库项目:SimpleCalculatorExtension

    因为需要实现IOperation ,所以需要添加对SimpleCalculator项目的引用。

    因为需要Export特性,所以需要添加对System.ComponentModel.Composition的引用。

    整个项目的结果如下:

    Subtract代码如下:

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.ComponentModel.Composition;

     

    namespace SimpleCalculatorExtension

    {

        [Export(typeof(SimpleCalculator.IOperation))]

        [ExportMetadata("Symbol", '-')]

        class Subtract : SimpleCalculator.IOperation

        {

            public int Operate(int left, int right)

            {

                return left - right;

            }

        }

    }

     

    生成成功后,将SimpleCalculatorExtension.dll 拷贝到CalculateExtensions目录下:

    现在SimpleCalculator的Debug目录应该是这样。

     

     

    并且CalculateExtensions文件夹下面有SimpleCalculatorExtension.dll.

     

    接下来唯一要修改的是Program的catalog 对象。

    为了让catalog既支持在Program的Assembly中查找,又支持在CalculateExtensions目录下查找。修改代码如下:

    public Program()

     {

         var catalog = new AggregateCatalog();

         catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

         catalog.Catalogs.Add(new DirectoryCatalog("CalculateExtensions"));

     

         _container = new CompositionContainer(catalog);

     

         try

         {

             this._container.ComposeParts(this);

         }

         catch(CompositionException compositionException)

         {

             Console.WriteLine(compositionException.ToString());

         }

     }

     

    运行结果如下:

     

     

    修改SimpleCalculatorExtension 的Subtract方法为:

    namespace SimpleCalculatorExtension

    {

        [Export(typeof(SimpleCalculator.IOperation))]

        [ExportMetadata("Symbol", '-')]

        class Subtract : SimpleCalculator.IOperation

        {

            public int Operate(int left, int right)

            {

                Console.WriteLine("SimpleCalculatorExtension的方法");

                return left - right;

            }

        }

    }

    重新生成SimpleCalculatorExtension.dll 然后拷贝到CalculateExtensions 文件夹下:

     http://www.cnblogs.com/LoveJenny/archive/2011/12/07/2278703.html

    再次运行程序,输出入下:

     http://www.cnblogs.com/LoveJenny/archive/2011/12/07/2278703.html

     

  • 相关阅读:
    链表--判断一个链表是否为回文结构
    矩阵--“之”字形打印矩阵
    二叉树——平衡二叉树,二叉搜索树,完全二叉树
    链表--反转单向和双向链表
    codeforces 490C. Hacking Cypher 解题报告
    codeforces 490B.Queue 解题报告
    BestCoder19 1001.Alexandra and Prime Numbers(hdu 5108) 解题报告
    codeforces 488A. Giga Tower 解题报告
    codeforces 489C.Given Length and Sum of Digits... 解题报告
    codeforces 489B. BerSU Ball 解题报告
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2279746.html
Copyright © 2011-2022 走看看