Strategy Pattern 與 Template Method Pattern 的相似,都是跟流程相關的模式 但有一些不一樣的地方
正如 Strategy Pattern 這個名子,這個模式著重在策略的部份 策略也是一種抽象的概念,可以說是達成某個目的過程中可能會用到方法 根據不同的場合使用不同的策略,靈活度高
Strategy Pattern 通常會透過一個介面來表示策略的抽象概念 接著只要把這個策略帶入任何需要使用的流程內即可
接下來會用常見的 Sorter 來做範例:
// 這邊簡單用個 selection sort :)
public class SelectionSorter<T> {
// 比較的策略,會回傳數字, 1 表示 A > B,0 表示相等,-1 表示 B < A
private CompareStrategy<T> strategy;
public Sorter(CompareStrategy<T> strategy) {
this.strategy = strategy;
}
// 由小到大排序
public void Sort(T[] items) {
for (var i=0;i<items.Length;++i) {
for (var j=i+1;j<items.Length;++j) {
if (this.strategy.Compare(items[i], items[j]) == 1) {
var temp = items[i];
items[i] = items[j];
items[j] = temp;
}
}
}
}
}
在上面的程式中,一個叫 Sort 泛型類別會接受一個 CompareStrategy 的泛型介面
之後就可以用這個 Sorter 對元素進行 in-place 排序
有關 CompareStrategy 的定義如下:
public interface CompareStrategy<T> {
int Compare(T item1, T item2);
}
這個 CompareStrategy 就是一個策略,用於比較兩個數值的大小
現在要做一個複數的排序,直接用 .NET 內建的 Complex 來表示複數 先以複數的大小來排序,然後再依照實數、虛數的數值個別排序:
public class MyComplexCompareStrategy : CompareStrategy<Complex> {
public int Compare(Complex item1, Complex item2) {
if (item1.Magnitude != item2.Magnitude) {
return item1.Magnitude > item2.Magnitude ? 1 : -1;
} else if (item1.Real != item2.Real) {
return item1.Real > item2.Real ? 1 : -1;
} else if (item1.Imaginary != item2.Imaginary) {
return item1.Imaginary > item2.Imaginary ? 1 : -1;
} else { // 到這邊就表示完全相等
return 0;
}
}
}
然後建立一個複數的比較並進行排序:
var complexCompareStrategy = new MyComplexCompareStrategy();
var complexSorter = new SelectionSorter<Complex>(complexCompareStrategy);
var complexList = new Complex[] {
new Complex(1, 2),
new Complex(0, 0),
new Complex(2, 1),
new Complex(3, 4),
new Complex(-2, 1)
};
Console.WriteLine("Before: {0}", string.Join(", ", complexList));
// Before: <1; 2>, <0; 0>, <2; 1>, <3; 4>, <-2; 1>
complexSorter.Sort(complexList);
Console.WriteLine("Sorted: {0}", string.Join(", ", complexList));
// Sorted: <0; 0>, <-2; 1>, <1; 2>, <2; 1>, <3; 4>
結果如下:

可以再更簡單一些
看過了上面的範例,應該可以感受到這個模式的威力 搭配泛型使用,整個類別變得很有彈性,適合不同的場合使用
原始的 Strategy Pattern 就是這種有介面與實做類別的形式,寫起來 Class 的數量很容易爆增 但由於現在的 OO 語言多少都會融合一點 FP 的概念 Lambda 的導入可以說避免產生太多的 Class 我們可以直接把上面的程式化簡化這樣:
public class SortAlgorithm {
// comparer 接受 2 個 T 參數,回傳 int
public void SelectionSort<T>(T[] items, Func<T, T, int> compare) {
for (var i=0;i<items.Length;++i) {
for (var j=i+1;j<items.Length;++j) {
if (compare(items[i], items[j]) == 1) {
var temp = items[i];
items[i] = items[j];
items[j] = temp;
}
}
}
}
}
執行的程式可以簡化成:
var comparer = (Complex item1, Complex item2) => {
if (item1.Magnitude != item2.Magnitude) {
return item1.Magnitude > item2.Magnitude ? 1 : -1;
} else if (item1.Real != item2.Real) {
return item1.Real > item2.Real ? 1 : -1;
} else if (item1.Imaginary != item2.Imaginary) {
return item1.Imaginary > item2.Imaginary ? 1 : -1;
} else { // 到這邊就表示完全相等
return 0;
}
}
var complexList = new Complex[] {
new Complex(1, 2),
new Complex(0, 0),
new Complex(2, 1),
new Complex(3, 4),
new Complex(-2, 1)
};
Console.WriteLine("Before: {0}", string.Join(", ", complexList));
// Before: <1; 2>, <0; 0>, <2; 1>, <3; 4>, <-2; 1>
SortAlgorithm.SelectionSort(complexList, comparer);
Console.WriteLine("Sorted: {0}", string.Join(", ", complexList));
// Sorted: <0; 0>, <-2; 1>, <1; 2>, <2; 1>, <3; 4>
除了策略本身透過可以 lambda 簡化並直接存到變數以外 流程本身也可以只是一個方法,然後接受一個函數來使用
這其實就跟在 C 裡面用函式指標是同一個意思 我會把這邊的 compare 函數變數作為是一種策略
做到這邊,最後就會發現,.NET 的 Array.Sort 跟範例很像:
public delegate int Comparison<in T>(T x, T y);
public static void Sort<T>(T[] array, Comparison<T> comparison);
思考模式的概念
從最開始有提過,Strategy Pattern 著重在策略的部份 策略就是這個模式的核心 將外部提供的策略注入到某個類別中使用
概念上就好比一個產品從生產到進入市場的流程 市場調查、研發、生產、最後打入市場 打入市場勢必需要某種策略,針對不同的客群使用不同的方式讓客戶對產品產生興趣
因為只關注策略而非流程,因此策略本身可以在各種流程中使用,提高了靈活度
與 Template Method Pattern 之間該如何做選擇
Template Method Pattern 與 Strategy Pattern 可以做同樣的事情,在靈活度跟關聯程度上也所差異 我覺得要從問題本身來思考:
現在要解決的問題是需要一套共通的作法,還是依場合套用不同策略?
了解問題的需求,可以從兩個模式中做選擇了
例如說排序,通常都會是使用者自己按需求排序 它需要使用者選擇一個比較策略,所以就會使用 Strategy Pattern
但如果在我的應用程式中,排序跟它所屬的環境有關 換句話說,它跟環境之間有比較強烈的關聯性,其他人不會隨意去更動比較規則 那使用 Template Method Pattern 就會很適合
後記
最近太懶了,一直沒有更新設計模式 雖然我也沒辦法寫太快,畢竟我需要一些例子來說明模式 有些模式實在是不太常見,我自己也很難想像什麼時候會用到 可能是因為我經驗還不夠,還沒見過各種優秀 (或慘絕人寰?) 的程式
在 **無暇的程式碼—敏捷完整篇 **書中,Strategy Pattern 跟 Template Method Pattern 是一起講的 書中的作者因為 Template Method Pattern 比較簡單而選擇使用該模式 但我自己的想法是,要去思考問題背後的抽象概念,根據關注的東西來選擇模式的使用 不過問題的概念可能隨著需求而有所改變,最後還是有機會轉去另一個模式
目前我對於設計模式的理解都來自 無暇的程式碼—敏捷完整篇 這本書 但實際上我並沒有特別去看過原本的設計模式書籍,頂多上網看一點文章而已,並不了解模式的背景故事 如果有什麼錯誤的地方,或是覺得解釋不好的,還請多多包含 <(_ _)>