關於.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模式可以「高內聚,低耦合」,大大降低了代碼的解耦,每次使一個銀行接入銀行的時候,完全不影響其他銀行的正常業務操作。