关于.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\”,编译程序,生成文件如下图:
然后测试解决方案Ioc容器加载的目录也需修改为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模式可以“高内聚,低耦合”,大大降低了代码的耦合,每增加一个银行接入银联的时候,完全不影响其他银行的正常业务操作。
四、示例代码地址
代码地址:https://github.com/Dwayne112401/MEFDemo.git