
在現實生活中,我們的筆記本電腦的工作電壓大多數都是20V,而我國的家庭用電是220V,如何讓20V的筆記本電腦能夠工作在220V的電壓下工作?答案:引入一個電源擴充卡,俗稱變壓器,有了這個電源擴充卡,生活用電和筆記本電腦即可相容。在軟體開發中,有時候也會存在這種不相容的情況,我們也可以像電源擴充卡一樣引入一個稱之為擴充卡的角色來協調這些存在不相容的結構,這種設計方案即稱之為擴充卡模式。
在現實生活中,我們的筆記本電腦的工作電壓大多數都是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;
}
}
View Code
編譯并運作,結果如下圖所示:
四、擴充卡模式小結
4.1 主要優點
(1)将目标類和适配者類解耦,進而無須修改原有結構(隻需新增一個擴充卡類)
(2)增加了類的透明性(适配者類中的業務實作過程)和複用性(同一個适配者類可以在多個不同的系統中複用)
(3)靈活性和可擴充性很好(借助配置檔案和反射機制,可以友善地切換擴充卡,符合開閉原則)
4.2 應用場景
(1)系統需要使用一些現有的類,而這些類的接口(例如方法名)不符合系統的需要,甚至沒有這些類的源碼。
(2)想要建立一個可以複用的類,用于一些彼此之間沒有太大關聯的類,包括一些可能在将來引進的類一起工作。
參考資料
劉偉,《設計模式的藝術—軟體開發人員内功修煉之道》
作者:周旭龍
出處:http://edisonchou.cnblogs.com
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結。