MEF实现设计上的“松耦合”(一)

1、什么是MEF

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

也有人把MEF解释为“依赖注入”的一种方式,那么什么是“依赖注入”?如果这样解释,感觉越陷越深......根据博主的理解,了解MEF只需要抓住以下几个关键点:

(1)字面意思,可扩展的framework,或者叫可扩展的库。也就是说,使用MEF是为了提高程序的可扩展性。MEF会根据指定的导入导出自动去发现匹配的扩展,不需要进行复杂的程序配置。

(2)在设计层面上来说,为什么要使用MEF?为了“松耦合”!我们知道,程序设计有几个原则,“高内聚,低耦合”就是其中一个。使用MEF可以帮助我们减少内库之间的耦合。

当然,如果你之前压根都没有听说过MEF,那么即使看了我上面的解释,估计也还是云里雾里。没关系,如果此刻你还有兴趣,看了下面的Demo,相信你会有一个初步的认识。

2、为什么要使用MEF:上面已经解释过,为了程序的扩展和“松耦合”。

3、MEF的使用:

(1)MEF基础导入导出的使用:

MEF的使用步骤主要分三步:宿主MEF并组合部件、标记对象的导出、对象的导入使用。

我们先来看一个Demo。

class Program2
    {
     //导入对象使用
        [Import("chinese_hello")]
        public Person oPerson { set; get; }

        static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();
            var strRes = oProgram.oPerson.SayHello("李磊");
            Console.WriteLine(strRes);

            Console.Read();
        }

     //宿主MEF并组合部件
        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //将部件(part)和宿主程序添加到组合容器
            container.ComposeParts(this);
        }
    }

   public interface Person
    {
        string SayHello(string name);
    }

   //声明对象可以导出
    [Export("chinese_hello", typeof(Person))]
    public class Chinese : Person
    {
        public string SayHello(string name)
        {
            return "你好:" + name ;
        }
    }

    [Export("american_hello", typeof(Person))]
    public class American : Person
    {
        public string SayHello(string name)
        {
            return "Hello:" + name ;
        }
    }

得到结果:

我们来分析下这段代码:

//宿主MEF并组合部件
        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //将部件(part)和宿主程序添加到组合容器
            container.ComposeParts(this);
        }

这个方法表示添加当前Program2这个类到组合容器,为什么要添加到组合容器?是因为只要添加到组合容器中之后,如果该类里面有Import,MEF才会自动去寻找对应的Export。这也就是为什么使用MEF前必须要组合部件的原因。

[Export("chinese_hello", typeof(Person))]
    public class Chinese : Person
    {
        public string SayHello(string name)
        {
            return "你好:" + name ;
        }
    }

这里的[Export("chinese_hello", typeof(Person))]这个特性表示标记Chinese类的导出。

将Export转到定义可以看到:

//
        // 摘要:
        //     通过在指定协定名称下导出指定类型,初始化 System.ComponentModel.Composition.ExportAttribute 类的新实例。
        //
        // 参数:
        //   contractName:
        //     用于导出使用此特性标记的类型或成员的协定名称,或 null 或空字符串 ("") 以使用默认协定名称。
        //
        //   contractType:
        //     要导出的类型。
        public ExportAttribute(string contractName, Type contractType);

这里的两个参数:第一个表示协定名称,如果找到名称相同的Import,那么就对应当前的Chinese对象;第二个参数表示要导出的类型。

[Import("chinese_hello")]
 public Person oPerson { set; get; }

这里的chinese_hello是和Export里面的chinese_hello对应的,由此可知,每一个[Import("chinese_hello")]这种Import一定可以找到一个对应的Export,如果找不到,程序就会报异常。当然如果这里的Import如果改成[Import("american_hello")],那么oPerson肯定就对应一个American对象。

通过上面的程序可以知道,我们使用[Import]这个特性,它的底层其实就是给我们初始化了一个对象。例如上面的[Import("chinese_hello")]等价于Person oPerson=new Chinese();。看到这里可能有人就会说这个Import是多此一举了,既然我们可以new,为什么非要用这种奇怪的语法呢,怪别扭的。其实如果我们站在架构的层面,它的好处就是可以减少dll之间的引用。

(2)MEF导入导出扩展:

按照MEF的约定,任何一个类或者是接口的实现都可以通过[System.ComponentModel.Composition.Export] 属性将其他定义组合部件(Composable Parts),在任何需要导入组合部件的地方都可以通过在特定的组合部件对象属性上使用[System.ComponentModel.Composition.Import ]实现部件的组合,两者之间通过契约(Contracts)进行通信。通过上面的例子我们可以知道,对象是可以通过Import和Export来实现导入和导出的,那么我们进一步扩展,对象的属性、字段、方法、事件等是否也可以通过[ImportAttribute]进行导入呢?

class Program2
    {
        [Import("TestProperty")]
        public string ConsoleTest { get; set; }

        static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();

            Console.WriteLine(oProgram.ConsoleTest);

            Console.Read();
        }

        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //将部件(part)和宿主程序添加到组合容器
            container.ComposeParts(this);
        }
    }

    public class TestPropertyImport
    {
        [Export("TestProperty")]
        public string TestMmport { get { return "测试属性可以导入导出"; } }
    }

得到结果:

由此说明,属性也是可以导入导出的。原理与上类似。既然属性可以,那么字段就不用演示了,它和属性应该是类似的。

下面来看看方法是否可以呢?

class Program2
    {
        [Import("chinese_hello")]
        public Person oPerson { set; get; }

        [Import("TestProperty")]
        public string ConsoleTest { get; set; }

        [Import("helloname")]
        public Action<string> TestFuncImport { set; get; }

        static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();
            oProgram.TestFuncImport("Jim");

            //Console.WriteLine(oProgram.ConsoleTest);
            //var strRes = oProgram.oPerson.SayHello("李磊");
            //Console.WriteLine(strRes);
            Console.Read();
        }

        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //将部件(part)和宿主程序添加到组合容器
            container.ComposeParts(this);
        }
    }

    public class TestPropertyImport
    {
        [Export("TestProperty")]
        public string TestMmport { get { return "测试属性可以导入导出"; } }

        [Export("helloname", typeof(Action<string>))]
        public void GetHelloName(string name)
        {
            Console.WriteLine("Hello:" + name);
        }
    }

由此可知,方法的导入和导出是通过匿名委托的方式实现的,那么由此类推,事件应该也是可以的,有兴趣的朋友可以一试。原理和上面是一样一样的。

既然属性、字段、方法、事件都可以通过Import和Export实现单一对象或变量的导入和导出,那么如果我们想要一次导入多个对象呢?嘿嘿,微软总是体贴的,它什么都为我们考虑到了。我们来看看如何实现。

class Program2
    {
        [ImportMany]
        public IEnumerable<Person> lstPerson { set; get; }

        static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();

            Console.WriteLine(oProgram.lstPerson.Count());

            Console.Read();
        }

        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //将部件(part)和宿主程序添加到组合容器
            container.ComposeParts(this);
        }
    }

public interface Person
    {
        string SayHello(string name);
    }

    [Export(typeof(Person))]
    public class Chinese : Person
    {
        public string SayHello(string name)
        {
            return "你好:" + name ;
        }
    }

    [Export(typeof(Person))]
    public class American : Person
    {
        public string SayHello(string name)
        {
            return "Hello:" + name ;
        }
    }

得到的结果为2。这里有一点需要注意的,使用ImportMany的时候对应的Export不能有chinese_hello这类string参数,否则lstPerson的Count()为0.

(3)MEF的延迟加载

我们知道,当装配一个组件的时候,当前组件里面的所有的Import的变量都自动去找到对应的Export而执行了实例化,有些时候,出于程序效率的考虑,不需要立即实例化对象,而是在使用的时候才对它进行实例化。MEF里面也有这种延迟加载的机制。

class Program2
    {
        [Import("chinese_hello")]
        public Person oPerson { set; get; }

        [Import("american_hello")]
        public Lazy<Person> oPerson2 { set; get; }

     static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();

            var strRes = oProgram.oPerson.SayHello("李磊");
            var strRes2 = oProgram.oPerson2.Value.SayHello("Lilei");
            Console.WriteLine(strRes);

            Console.Read();
        }

        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //将部件(part)和宿主程序添加到组合容器
            container.ComposeParts(this);
        }
    }

   public interface Person
    {
        string SayHello(string name);
    }

    [Export("chinese_hello", typeof(Person))]
    public class Chinese : Person
    {
        public string SayHello(string name)
        {
            return "你好:" + name ;
        }
    }

    [Export("american_hello", typeof(Person))]
    public class American : Person
    {
        public string SayHello(string name)
        {
            return "Hello:" + name ;
        }
    }

通过调试可知,当程序运行到var strRes = oProgram.oPerson.SayHello("李磊");这一行的时候

oPerson对象已经实例化了,而oPerson2.Value对象没有实例化,当程序执行var strRes2 = oProgram.oPerson2.Value.SayHello("Lilei")这一句的时候,oPerson2.Value对象才进行实例化。这种需要在某些对程序性能有特殊要求的情况下面有一定的作用。

讲到这里,我们再来看前面关于理解MEF的两个关键点:

(1)可扩展的库:由于MEF允许通过Import的方式直接导入对象、属性、方法等,试想,有人开发了一个组件,他们事先定义好了一系列的导出(Export),我们只需要将它的组件引进来,使用Import的方式按照他们Export的约定导入对象即可,不用做其他复杂的配置。

(2)能更好的实现“松耦合”:比如我们项目按照面向接口编程的方式这样分层:UI层、BLL接口层、BLL实现层......UI层只需要引用BLL接口层即可,我们在BLL实现层里面定义好Export的导出规则,然后再UI层里面使用Import导入BLL实现层的对象即可,这样UI层就不需要添加BLL实现层的引用。减少了dll之间的依赖。

以上就是MEF的一些基础用法。当然在实际使用中可能不会这么简单,但是再复杂的用法都是在这些简单基础上面扩展起来的。

时间: 2024-10-14 01:55:56

MEF实现设计上的“松耦合”(一)的相关文章

C#进阶系列——MEF实现设计上的“松耦合”(四):构造函数注入

前言:今天十一长假的第一天,本因出去走走,奈何博主最大的乐趣是假期坐在电脑前看各处堵车,顺便写写博客,有点收获也是好的.关于MEF的知识,之前已经分享过三篇,为什么有今天这篇?是因为昨天分享领域服务的时候,用到MEF的注入有参构造函数的方法,博主好奇心重,打算稍微深挖一下,这篇来对此知识点做个总结. 还是将前面三篇的目录列出来,对MEF没有了解的朋友,可以先看看: C#进阶系列——MEF实现设计上的“松耦合”(一) C#进阶系列——MEF实现设计上的“松耦合”(二) C#进阶系列——MEF实现设

MEF实现设计上的“松耦合”

C#进阶系列——MEF实现设计上的“松耦合”(二) 前言:前篇 C#进阶系列——MEF实现设计上的“松耦合”(一) 介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能不限于此,比如MEF的目录服务.目录筛选.重组部件等高级应用在这里就不做过多讲解,因为博主觉得这些用法只有在某些特定的环境下面才会用到,着实不太普遍,感觉没有钻下去的必要.如果你有兴趣也可以去了解下.这篇打算将MEF和仓储模式结合起来谈谈MEF在项目中的使用. 1.仓储模式:也叫Repository模式

C#进阶系列——MEF实现设计上的“松耦合”(二)

前言:前篇 C#进阶系列——MEF实现设计上的“松耦合”(一) 介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能不限于此,比如MEF的目录服务.目录筛选.重组部件等高级应用在这里就不做过多讲解,因为博主觉得这些用法只有在某些特定的环境下面才会用到,着实不太普遍,感觉没有钻下去的必要.如果你有兴趣也可以去了解下.这篇打算将MEF和仓储模式结合起来谈谈MEF在项目中的使用. 1.仓储模式:也叫Repository模式.Repository是一个独立的层,介于领域层与数

C#进阶系列——MEF实现设计上的“松耦合”(一)

前言:最近去了趟外地出差,介绍推广小组开发的框架类产品.推广对象是本部门在项目上面的同事——1到2年工作经验的初级程序员.在给他们介绍框架时发现很多框架设计层面的知识他们都没有接触过,甚至没听说过,这下囧了~~于是乎在想该如何跟他们解释MEF.AOP.仓储模式等方面的东东.本来 C#基础系列 应该还有两篇关于异步的没有写完,奈何现在要推广这些个东西,博主打算先介绍下项目中目前用到的些技术,异步的往后有时间再做分享.C#进阶系列主要围绕MEF.AOP.仓储模式.Automapper.WCF等展开.

MEF实现设计上的“松耦合”(三)

1.面向接口编程:有一定编程经验的博友应该都熟悉或者了解这种编程思想,层和层之间通过接口依赖,下层不是直接给上层提供服务,而是定义一组接口供上层调用.至于具体的业务实现,那是开发中需要做的事情,在项目架构阶段,只需要定义好层与层之间的接口依赖,将框架搭起来,编译可以直接通过.为什么要有这么一种设计?既然是架构设计,当然是为了提高架构的灵活性,降低层和层之间的依赖(耦合).这个并非一句两句讲得清楚的,更多详细可以参看:面向接口编程详解(一)——思想基础.此文我觉得分析比较到位.好了,不说废话,来看

MEF实现设计上的“松耦合”(二)

介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能不限于此,比如MEF的目录服务.目录筛选.重组部件等高级应用在这里就不做过多讲解,因为博主觉得这些用法只有在某些特定的环境下面才会用到,着实不太普遍,感觉没有钻下去的必要.如果你有兴趣也可以去了解下.这篇打算将MEF和仓储模式结合起来谈谈MEF在项目中的使用. 1.仓储模式:也叫Repository模式.Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间.它的存在让领域层感觉不到数据访问层的

Prism 4 文档 ---第9章 松耦合组件之间通信

当构建一个大而负责的应用程序时,通用的做法时将功能拆分到离散的模块程序集中.将模块之间的静态引用最小化.这使得模块可以被独立的开发,测试,部署和升级,以及它迫使松散耦合的沟通. 当在模块之间通信时,你需要知道不同通信方式之间的区别,那样你才能确定哪种方式对于你的特定的场景最合适,Prism类库提供了以下几种通信方式: 命令.当希望对于用户的交互马上采取动作时使用. 事件聚合.用于ViewModel,展现层,或者控制之间没有所期望的直接动作时. 区域上下文.使用它可以提供宿主和宿主区域的View之

松耦合和紧耦合

Question:首先,明确一点,什么是松耦合?什么是紧耦合? Answer:比如说两个模块,A模块和B模块,当两者的关联非常多的时候,就叫紧耦合,反之,则是松耦合. 实现松耦合的方式有,使用接口抽象出来,当两个模块的关联仅仅是根据几个接口就可以实现的话,那么,就应当叫松耦合,实现松耦合是非常有必要的. By the way,在软件设计中,应该把层次尽量分开,多分几层,每一层各尽其职,高内聚,低耦合,同时,可以将两个模块的关系也抽象出来,比如说IOC/DI的设计模式,有人说spring的IOC/

Spring松耦合示例(转)

Spring松耦合示例 面向对象设计的理念是把整个系统分成一组可重用的组件,然而,当系统变得越大的时候,尤其是在java中,这最大的对象依赖将会紧紧耦合,以至于非常难以管理和修改,而现在,你可以使用Spring框架扮演一个中间模块的角色,方便高效地管理其他组件依赖 输出生成的例子 看个例子,假设你的项目有一个方法可以输出内容到csv或者json格式,你可能写出这样的代码 package com.mkyong.output; public interface IOutputGenerator {