关于.NET中的控制反转(二)- 依赖注入之 MEF

一、MEF是什么

       Managed Extensibility Framework (MEF) 是用于创建可扩展的轻量级应用程序的库。 它让应用程序开发人员得以发现和使用扩展且无需配置。 它还让扩展开发人员得以轻松地封装代码并避免脆弱的紧密依赖性。 MEF 让扩展不仅可在应用程序内重复使用,还可以跨程序重复使用。

      MEF 通过组合提供了一种隐式发现它们的方法,而不是明确记录可用组件。 MEF 组件(称为一个部件),以声明方式详细说明了其依赖项(称为导入)及其可提供的功能(称为导出)。 当创建一个部分时,MEF 组合引擎利用从其他部分获得的功能满足其导入需要。

     一句话,MEF就是面向接口编程的应用,接口定义行为,它把实例化类放到代码运行的时候,通过容器参数确定。

二、MEF示例

       在我们国家,不管你在哪个银行办理银行卡,只要银行卡有银联标识,那么你就基本可以在任意一家银行取钱、存钱。中国银联(China UnionPay)成立于2002年3月,是经国务院同意,中国人民银行批准设立的中国银行卡联合组织,在境内的银行必须都支持银联。也就是说,中国银联定义了一些行为,例如用户可以取钱、存钱,任意一家境内银行都必须支持银联定义的行为。因此,你在任意一家银行办理银行卡后,就可以在所有的银行都进行取钱、存钱等及行为。下面我们以此为例,进行一个简单的例子:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel.Composition;
  4 using System.ComponentModel.Composition.Hosting;
  5 using System.Linq;
  6 using System.Reflection;
  7 using System.Text;
  8 using System.Threading.Tasks;
  9 
 10 namespace MEF1
 11 {
 12     class Operation
 13     {
 14         static void Main(string[] args)
 15         {
 16             BlankOperation("CBC",300,100);
 17             Console.WriteLine("------------------------------");
 18             BlankOperation("BOC",888,666);
 19             Console.ReadKey();
 20         }
 21 
 22         static void BlankOperation(string bankName,int saveMonenyAmout,int withdrawMoneyAmount)
 23         {
 24             var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
 25             CompositionContainer container = new CompositionContainer(catalog);
 26             var dev = container.GetExportedValue<IUnionPay>(bankName);
 27             dev.SaveMoneny(saveMonenyAmout);
 28             dev.WithdrawMoney(withdrawMoneyAmount);
 29         }
 30     }
 31 
 32   
 33     interface IUnionPay
 34     {
 35         /// <summary>
 36         /// 存钱
 37         /// </summary>
 38         /// <param name="amount">存钱金额</param>
 39         void SaveMoneny(int amount);
 40 
 41         /// <summary>
 42         /// 取钱
 43         /// </summary>
 44         /// <param name="amount">取钱金额</param>
 45         void WithdrawMoney(int amount);
 46 
 47     }
 48 
 49     /// <summary>
 50     /// 工商银行
 51     /// </summary>
 52     [Export("CBC",typeof(IUnionPay))]
 53     class ICBC : IUnionPay
 54     {
 55         public void SaveMoneny(int amount)
 56         {
 57             Console.WriteLine($"把钱存入工商银行,金额为:{amount}");
 58         }
 59 
 60         public void WithdrawMoney(int amount)
 61         {
 62             Console.WriteLine($"从工商银行取钱,金额为:{amount}");
 63         }
 64     }
 65 
 66     /// <summary>
 67     /// 建设银行
 68     /// </summary>
 69     [Export("CCB", typeof(IUnionPay))]
 70     class CCB : IUnionPay
 71     {
 72         public void SaveMoneny(int amount)
 73         {
 74             Console.WriteLine($"把钱存入建设银行,金额为:{amount}");
 75         }
 76 
 77         public void WithdrawMoney(int amount)
 78         {
 79             Console.WriteLine($"从建设银行取钱,金额为:{amount}");
 80         }
 81     }
 82 
 83     /// <summary>
 84     /// 农业银行
 85     /// </summary>
 86     [Export("ABC", typeof(IUnionPay))]
 87     class ABC : IUnionPay
 88     {
 89         public void SaveMoneny(int amount)
 90         {
 91             Console.WriteLine($"把钱存入建设银行,金额为:{amount}");
 92         }
 93 
 94         public void WithdrawMoney(int amount)
 95         {
 96             Console.WriteLine($"从建设银行取钱,金额为:{amount}");
 97         }
 98     }
 99 
100     /// <summary>
101     /// 中国银行
102     /// </summary>
103     [Export("BOC", typeof(IUnionPay))]
104     class BOC : IUnionPay
105     {
106         public void SaveMoneny(int amount)
107         {
108             Console.WriteLine($"把钱存入建设银行,金额为:{amount}");
109         }
110 
111         public void WithdrawMoney(int amount)
112         {
113             Console.WriteLine($"从建设银行取钱,金额为:{amount}");
114         }
115     }
116 }

代码运行后,效果如下:

三、MEF示例改进

      那如果我们在增加一个银行实现类,例如招商银行,还需要在MEF1解决方案中增加一个 “CMB”类。但如果我们的程序以及运行了,那如果在不关闭程序的前提下,还完成支持招商银行接入银联卡呢?

     答案是为每个银行实现类创建一个解决方案,然后编译成dll文件,这样我们在支持招商银行接入银联时,只需要把 “CMB.dll”文件放入支持文件,就可以即不关闭主程序,还可以无缝支持招商银行接入银联:

     我们把实现银联的银行的解决方案的生成目录保存在目录 “..\bin\Debug\bank\”,用于测试的解决方案和银联接口的解决方案生成目录保存在目录 “..\bin\Debug\”,编译程序,生成文件如下图:

      

  然后测试解决方案LOC容器加载的目录也需修改为bank目录下:

 1 using ChinaUnionPay;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.ComponentModel.Composition.Hosting;
 5 using System.IO;
 6 using System.Linq;
 7 using System.Reflection;
 8 using System.Text;
 9 using System.Threading.Tasks;
10 
11 namespace BankOperation
12 {
13     class Program
14     {
15         static void Main(string[] args)
16         {
17             while (true)
18             {
19                 Console.Write($"请输入银行名称:");
20                 string name = Console.ReadLine();
21                 BlankOperation(name, 300, 100);
22                 Console.WriteLine("----------------------------------");
23             }
24         }
25 
26         static void BlankOperation(string bankName, int saveMonenyAmout, int withdrawMoneyAmount)
27         {
28             AggregateCatalog catelog = new AggregateCatalog();
29 
30             // 添加部件所在文件目录
31             string path = $"{Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath)}\\bank\\";
32             catelog.Catalogs.Add(new DirectoryCatalog(path));
33 
34             // 声明容器
35             CompositionContainer container = new CompositionContainer(catelog);
36             var dev = container.GetExportedValue<IUnionPay>(bankName);
37 
38             // 动作调用
39             dev.SaveMoneny(saveMonenyAmout);
40             dev.WithdrawMoney(withdrawMoneyAmount);
41         }
42     }
43 }

此时运行测试程序,运行效果如下:

 

 

 此时,若在程序运行的时候添加招商银行,我们需添加招商银行的解决方案如下,然后编译生成:

 1 using ChinaUnionPay;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.ComponentModel.Composition;
 5 using System.Linq;
 6 using System.Text;
 7 using System.Threading.Tasks;
 8 
 9 namespace CMB
10 {
11     /// <summary>
12     /// 招商银行
13     /// </summary>
14     [Export("CMB",typeof(IUnionPay))]
15     public class Operation : IUnionPay
16     {
17         public void SaveMoneny(int amount)
18         {
19             Console.WriteLine($"把钱存入招商银行,金额为:{amount}");
20         }
21 
22         public void WithdrawMoney(int amount)
23         {
24             Console.WriteLine($"从招商银行取钱,金额为:{amount}");
25         }
26     }
27 }

运行效果如下:

我们发现,使用MEF模式可以“高内聚,低耦合”,大大降低了代码的解耦,每次使一个银行接入银行的时候,完全不影响其他银行的正常业务操作。