关于.NET中的控制反转(二)- 依赖注入之 MEF
- 2021 年 1 月 7 日
- 筆記
一、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模式可以“高内聚,低耦合”,大大降低了代码的解耦,每次使一个银行接入银行的时候,完全不影响其他银行的正常业务操作。