瞭解策略模式–通過詳細代碼與例子
應用場景
完成一項任務,往往可以有多種策略可以實現,要想靈活選擇策略和添加新策略,可以使用策略模式。
比如從數組中查找某個值的任務,我們可以從頭到尾遍歷查找,可以從尾到頭遍歷查找,如果數組有序,還可以用二分法查找。不同的數組可以採用不同的策略來實現查找任務,我們還想讓數組的查找方式能設置與修改,這時候就可以用上策略模式,把不同的查找方法封裝成獨立的類,數組可以自行選擇使用哪種查找策略。
定義
策略模式定義了一系列策略,每一個策略都封裝起來,並讓他們可以相互轉換,使用策略的客戶可以任意選擇策略,策略的變化獨立於使用策略的客戶
例子
就用上面的數組查值爲例子,任務是從數組中查找某個值,策略有從頭到尾查找和從尾到頭查找,策略獨立於數組類,下面是UML類圖
NumberArray爲抽象類,內部屬性有protected的arr 和searchAlgorithm ,arr是Number數組,存放數據,searchAlgorithm是接口ISearchAlgorithm類型,後者定義了find方法用於從數據中查找某個數。實現了該接口的類有LeftSearchAlgorithm和RightSearchAlgorithm,分別實現了從頭到尾和從尾到頭查找數值的策略。NumberArray的子類有IntegerArray和DoubleArray,分別是整數數組和double小數數組,下面是代碼
ISearchAlgorithm.java
package priv.mxz.design_pattern.strategy_pattern;
interface ISearchAlgorithm {
int find(Number[] arr, Number num);
}
class LeftSearchAlgorithm implements ISearchAlgorithm {
@Override
public int find(Number[] arr,Number num) {
if (arr==null)
return -1;
for (int i=0; i<arr.length; i++){
if (arr[i].equals(num))
return i;
}
return -1;
}
@Override
public String toString() {
return "strategy_pattern.LeftSearchAlgorithm";
}
}
class RightSearchAlgorithm implements ISearchAlgorithm {
@Override
public int find(Number[] arr, Number num) {
if (arr==null)
return -1;
for (int i=arr.length-1; i>=0; i--){
if (arr[i].equals(num))
return i;
}
return -1;
}
@Override
public String toString() {
return "strategy_pattern.RightSearchAlgorithm";
}
}
NumberArray.java
package priv.mxz.design_pattern.strategy_pattern;
import java.util.Arrays;
abstract class NumberArray {
protected Number[] arr;
protected ISearchAlgorithm searchAlgorithm;
public NumberArray(Number[] arr){
this.arr=arr;
}
int find(Number num){
return searchAlgorithm.find(arr,num);
}
public void setSearchAlgorithm(ISearchAlgorithm searchAlgorithm){
this.searchAlgorithm=searchAlgorithm;
}
@Override
public String toString() {
return "strategy_pattern.NumberArray{" +
"arr=" + Arrays.toString(arr) +
", searchAlgorithm=" + searchAlgorithm +
'}';
}
}
class IntegerArray extends NumberArray{
public IntegerArray(Integer[] arr,ISearchAlgorithm searchAlgorithm){
super(arr);
setSearchAlgorithm(searchAlgorithm);
}
}
class DoubleArray extends NumberArray{
public DoubleArray(Double[] arr,ISearchAlgorithm searchAlgorithm){
super(arr);
setSearchAlgorithm(searchAlgorithm);
}
}
StrategyPattern.java
package priv.mxz.design_pattern.strategy_pattern;
public class StrategyPattern {
public static void main(String[] args) {
IntegerArray integerArray=new IntegerArray(
new Integer[]{1,2,3,3,2,1},
new LeftSearchAlgorithm());
DoubleArray doubleArray=new DoubleArray(
new Double[]{1.0,2.0,3.0,3.0,2.0,1.0},
new LeftSearchAlgorithm());
System.out.println("strategy_pattern.IntegerArray: "+integerArray);
System.out.println("strategy_pattern.IntegerArray found 2 at index "+integerArray.find(2));
System.out.println("Change integerArray search algorithm to right");
integerArray.setSearchAlgorithm(new RightSearchAlgorithm());
System.out.println("strategy_pattern.IntegerArray: "+integerArray);
System.out.println("strategy_pattern.IntegerArray found 2 at index "+integerArray.find(2));
System.out.println("strategy_pattern.DoubleArray: "+doubleArray);
System.out.println("strategy_pattern.DoubleArray found 2 at index "+doubleArray.find(2.0));
}
}
StrategyPattern類中有執行入口,main函數中可以看到,初始化時integerArray和doubleArray的查找策略都設置爲從頭到尾查找,然後打印integerArray的信息和調用find方法查找2的位置,結果爲1,即數組中第一個2的index。隨後我們通過integerArray.setSearchAlgorithm(new RightSearchAlgorithm());修改integerArray的查找策略爲從尾到頭,打印信息和調用find方法查找2的位置,結果爲4,即數組中第二個2的index。最後打印doubleArray的信息和調用find方法,發現doubleArray的查找策略不收integerArray的影響,不會因爲IntegerArray策略而改變。
運行輸出結果如下
strategy_pattern.IntegerArray: strategy_pattern.NumberArray{arr=[1, 2, 3, 3, 2, 1], searchAlgorithm=strategy_pattern.LeftSearchAlgorithm}
strategy_pattern.IntegerArray found 2 at index 1
Change integerArray search algorithm to right
strategy_pattern.IntegerArray: strategy_pattern.NumberArray{arr=[1, 2, 3, 3, 2, 1], searchAlgorithm=strategy_pattern.RightSearchAlgorithm}
strategy_pattern.IntegerArray found 2 at index 4
strategy_pattern.DoubleArray: strategy_pattern.NumberArray{arr=[1.0, 2.0, 3.0, 3.0, 2.0, 1.0], searchAlgorithm=strategy_pattern.LeftSearchAlgorithm}
strategy_pattern.DoubleArray found 2 at index 1
優缺點
在上述的例子中,如果不使用策略模式,採用硬編碼把find函數的實現寫入NumberArray或者它的子類中,則會有下面的問題
- 如果在NumberArray中實現find方法,那麼所有的子類的find方法都默認是同一個find策略,我們假設使用從頭到尾策略,除非子類重寫覆蓋find方法,那麼如果有多個子類要使用從尾到頭策略,這些子類都需要重寫一份從尾到頭策略,造成代碼的重複。比如DoubleArray自己實現了從尾到頭策略,此時若LongArray也要實現從尾到頭策略,就需要把DoubleArray中的複製過來做修改,這樣就造成了多份類似甚至相同的代碼,如果NumberArray不實現find方法,則每個子類都要寫自己的find方法,代碼的重複度更高。
- 如果子類要修改查找策略,只能修改源代碼,如果要實現在運行時改變查找策略,也要修改源代碼,添加if else判斷來確定查找策略,或者分別定義find和rfind。而是用策略模式只需要調用setSearchAlgorithm方法
所以,使用策略模式的優點如下:
- 策略模式提供了對“開閉原則”的完美支持,用戶可以在不修改原有系統的基礎上選擇策略,也可以靈活地增加新策略。
- 策略模式提供了管理相關策略的辦法。(例子中的setSearchAlgorithm)
- 策略模式提供了可以替換繼承關係的辦法。
- 使用策略模式可以避免使用多重條件轉移語句。
策略模式的缺點如下:
- 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。
- 策略模式將造成產生很多策略類,可以通過使用享元模式在一定程度上減少對象的數量。