在現實生活中,我們的膝上型電腦的工作電壓大多數都是20V,而我國的家庭用電是220V,如何讓20V的膝上型電腦能夠工作在220V的電壓下工作?答案:引入一個電源介面卡,俗稱變壓器,有了這個電源介面卡,生活用電和膝上型電腦即可相容。
在軟體開發中,有時候也會存在這種不相容的情況,我們也可以像電源介面卡一樣引入一個稱之為介面卡的角色來協調這些存在不相容的結構,這種設計方案即稱之為介面卡模式。
介面卡模式(Builder) | 學習難度:★★☆☆☆ | 使用頻率:★★★★☆ |
一、木有原始碼的演算法庫
Background : M公司在很久以前曾經開發了一個演算法庫,裡面包含了一些常用的演算法,例如排序和查詢演算法,在進行各類軟體開發時經常需要重用該演算法庫中的演算法。在為某學校開發教務管理系統時,開發人員發現需要對學生成績進行排序和查詢。該系統的設計人員已經開發了一個成績操作介面IScoreOperation,在該介面中宣告瞭排序方法Sort(int[])和查詢方法Search(int[],int)。為了提高排序和查詢的效率,開發人員決定重用演算法庫中的快速排序演算法類QuickSort和二分查詢演算法類BinarySearch。但是,由於某些原因,現在M公司開發人員已經找不到該演算法庫的原始碼,無法直接通過複製合貼上操作來重用其中的程式碼;部分開發人員已經針對IScoreOperation介面編寫程式碼,如果這時再要求對該介面修改或者要求大家直接使用QuickSort類和BinarySearch類將會導致大量程式碼需要修改。
因此,M公司開發人員面對這個沒有遠嗎的演算法庫,遇到了一個幸福而又煩惱的問題:如何在既不修改現有介面又不需要任何演算法庫程式碼的基礎上實現演算法庫的重用?
通過分析,不難得知,現在M公司面對的問題有點類似於我們在最開始提到的電壓問題,成績操作介面IScoreOperation有點類似於只支援20V電壓的膝上型電腦,而演算法庫好比220V的家庭用電,這兩部分都沒法再進行修改,而且它們原本是兩個完全不相關的結構。
為了讓IScoreOperation介面與已有演算法庫一起工作,讓它們在同一個系統中能夠相容,最好的實現方法是增加一個類似電源介面卡一樣的介面卡角色,通過介面卡來協調這兩個原本不相容的結構。
二、介面卡模式簡介
2.1 介面卡模式定義
介面卡模式的實現就是把客戶類的請求轉化為對應適配者的相應介面的呼叫。也就是說:當客戶類呼叫介面卡的方法時,在介面卡類的內部將呼叫適配者類的方法,而這個過程對於客戶類來說是透明的,客戶類並不直接訪問適配者類。因此,介面卡讓那些由於介面不相容而不能互動的類可以一起工作。
介面卡(Adapter)模式:將一個介面轉換成客戶希望的另一個介面,使介面不相容的那些類可以一起工作。
2.2 介面卡模式主要角色
介面卡模式一般包含以下3個角色:
(1)Target(目標抽象類):目標抽象類定義了客戶所需要的介面,可以是一個抽象類或介面,也可以是一個具體的類。
(2)Adapter(介面卡類):介面卡可以呼叫另一個介面,作為一個轉換器,對Adaptee和Target進行適配。介面卡類是適配者模式的核心,在介面卡模式中,它通過繼承Target並關聯一個Adaptee物件使二者產生聯絡。
(3)Adaptee(適配者類):適配者即被適配的角色,它定義了一個已經存在的介面,這個介面需要適配,一般是一個具體類,包含了客戶希望使用的業務方法,在某些情況下可能沒有適配者類的原始碼。
三、藉助介面卡重用演算法庫
3.1 解決方案結構圖
其中,IScoreOpertion介面充當抽象目標,QuickSort和BinarySearch類充當適配者,而OperationAdapter充當介面卡。
3.2 具體實現
(1)Target(目標抽象類):
/// <summary> /// 目標介面:抽象成績操作類 /// </summary> public interface IScoreOperation { // 成績排序 int[] Sort(int[] array); // 成績查詢 int Search(int[] array, int key); }
(2)Adaptee(適配者類):
/// <summary> /// 適配者A:快速排序類 /// </summary> public class QuickSortHelper { public int[] QuickSort(int[] array) { Sort(array, 0, array.Length - 1); return array; } public void Sort(int[] array, int p, int r) { int q = 0; if (p < r) { q = Partition(array, p, r); Sort(array, p, q - 1); Sort(array, q + 1, r); } } public int Partition(int[] array, int p, int r) { int x = array[r]; int j = p - 1; for (int i = p; i <= r - 1; i++) { if (array[i] <= x) { j++; Swap(array, j, i); } } Swap(array,j+1,r); return j + 1; } public void Swap(int[] array, int i, int j) { int t = array[i]; array[i] = array[j]; array[j] = t; } }
public class BinarySearchHelper { public int BinarySearch(int[] array, int key) { int low = 0; int high = array.Length - 1; while (low <= high) { int mid = (low + high) / 2; int midVal = array[mid]; if (midVal < key) { low = mid + 1; } else if (midVal > key) { high = mid - 1; } else { return 1; // 找到元素返回1 } } return -1; // 未找到元素返回-1 } }
(3)Adapter(介面卡類):
/// <summary> /// 介面卡:成績操作介面卡類 /// </summary> public class OperationAdapter : IScoreOperation { private QuickSortHelper sortTarget; private BinarySearchHelper searchTarget; public OperationAdapter() { sortTarget = new QuickSortHelper(); searchTarget = new BinarySearchHelper(); } public int Search(int[] array, int key) { return searchTarget.BinarySearch(array, key); } public int[] Sort(int[] array) { return sortTarget.QuickSort(array); } }
(4)Client 客戶端測試程式碼
public class Client { public static void Main(string[] args) { IScoreOperation operation = (IScoreOperation)AppConfigHelper.GetAdapterInstance(); if (operation == null) { return; } int[] scores = { 84, 76, 50, 69, 90, 91, 88, 96 }; int[] result; int score; Console.WriteLine("測試成績排序結果:"); result = operation.Sort(scores); foreach (int s in result) { Console.Write("{0},", s.ToString()); } Console.WriteLine(); Console.WriteLine("查詢是否有90分的人:"); score = operation.Search(scores, 90); if (score == -1) { Console.WriteLine("抱歉,這個真沒找到~~~"); } else { Console.WriteLine("恭喜,的確存在90分選手~~~"); } Console.WriteLine("查詢是否有92分的人:"); score = operation.Search(scores, 92); if (score == -1) { Console.WriteLine("抱歉,這個真沒找到~~~"); } else { Console.WriteLine("恭喜,的確存在92分選手~~~"); } Console.ReadKey(); } }
為了讓系統具有良好的靈活性和可擴充套件性,引入了配置檔案和AppConfigHelper類。
其中,將具體的Adapter例項配置在配置檔案中,如果需要使用其他的排序演算法和查詢演算法類,可以增加一個新的介面卡類,使用新的介面卡來適配新的演算法,原有程式碼無需修改。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="AdapterName" value="Manulife.ChengDu.DesignPattern.Adapter.OperationAdapter, Manulife.ChengDu.DesignPattern.Adapter" /> </appSettings> </configuration>
AppConfigHelper主要用於讀取配置檔案並通過反射生成例項,可以在不修改客戶端程式碼地情況下使用新的介面卡,其具體程式碼如下:
public class AppConfigHelper { public static string GetAdapterName() { string factoryName = null; try { factoryName = System.Configuration.ConfigurationManager.AppSettings["AdapterName"]; } catch (Exception ex) { Console.WriteLine(ex.Message); } return factoryName; } public static object GetAdapterInstance() { string assemblyName = AppConfigHelper.GetAdapterName(); Type type = Type.GetType(assemblyName); var instance = Activator.CreateInstance(type); return instance; } }
編譯並執行,結果如下圖所示:
四、介面卡模式小結
4.1 主要優點
(1)將目標類和適配者類解耦,從而無須修改原有結構(只需新增一個介面卡類)
(2)增加了類的透明性(適配者類中的業務實現過程)和複用性(同一個適配者類可以在多個不同的系統中複用)
(3)靈活性和可擴充套件性很好(藉助配置檔案和反射機制,可以方便地切換介面卡,符合開閉原則)
4.2 應用場景
(1)系統需要使用一些現有的類,而這些類的介面(例如方法名)不符合系統的需要,甚至沒有這些類的原始碼。
(2)想要建立一個可以複用的類,用於一些彼此之間沒有太大關聯的類,包括一些可能在將來引進的類一起工作。
參考資料
劉偉,《設計模式的藝術—軟體開發人員內功修煉之道》