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