Java設計模式6:策略模式

五月的倉頡發表於2015-10-24

策略模式

策略模式的用意是針對一組演算法,將每一個演算法封裝到具有共同介面的獨立類中,從而使得它們可以相互替換。策略模式使得演算法可以在不影響到客戶端的情況下發生變化。

 

策略模式的結構

策略模式是對演算法的包裝,是把使用演算法的責任和演算法本身分開。策略模式通常是把一系列的演算法包裝到一系列的策略類裡面,作為一個抽象策略類的子類。

策略模式涉及到三個角色:

1、環境角色

持有一個策略Strategy的引用

2、抽象策略角色

這是一個抽象角色,通常由一個介面或抽象類實現,此角色給出所有具體策略類所需的介面

3、具體策略角色

包裝了相關演算法或行為

 

策略模式示例

有一個抽象的策略介面:

public interface Strategy
{
    public void useStrategy();
}

實現兩種具體的策略:

public class StrategyA implements Strategy
{
    public void useStrategy()
    {
        System.out.println("StrategyA.useStrategy()");
    }
}
public class StrategyB implements Strategy
{
    public void useStrategy()
    {
        System.out.println("StrategyB.useStrategy()");
    }
}

某個類持有策略的引用:

public class Context
{
    private Strategy strategy;
    
    public Context(Strategy strategy)
    {
        this.strategy = strategy;
    }
    
    public void strategyMethod()
    {
        strategy.useStrategy();
    }
}

呼叫這個類的地方可以自行決定使用哪種策略:

public class TestMain
{
    public static void main(String[] args)
    {
        Strategy strategyA = new StrategyA();
        Strategy strategyB = new StrategyB();
        
        Context context = new Context(strategyA);
        context.strategyMethod();
        context = new Context(strategyB);
        context.strategyMethod();
    }
}

 

策略模式的使用場景

1、購物系統

舉一個實際例子吧。假如有一個購物系統,在使用者付款的時候,會產生很多場景,根據使用者的不同情況算出不同使用者要付款的金額,這時候最直觀的一種做法是:

在付款的裡面寫N多的if...else if...else,判斷使用者的場景,根據場景計算使用者付款金額。

這種設計明顯違反了開閉原則。開閉原則的"閉",指的是對修改關閉,但是這裡假如演算法又多了幾種,那麼必須再次修改這個付款的類。

這時候就可以使用策略模式。在付款的類裡面持有一個付款介面的引用,每次根據不同場景傳入一個具體的策略就好了。比如A類中要使用S0演算法,就傳入一個S0策略;B類中要使用S1演算法,就傳入一個S1演算法。不需要把判斷都放在付款的類中,程式碼的可讀性、可維護性也更高了。付款這個類甚至可以直接生成一個.class檔案放在一個jar包裡面供呼叫。

2、使得程式碼更優雅、更易維護

假如你的程式碼中某處有一個打分系統,你為這個打分系統寫了一段非常長的邏輯,某天,產品部門的同事找你,給我換一段打分邏輯,此時,有兩種做法:

(1)把原有的打分邏輯刪除,但這麼做一個缺點是是看不到以前的打分演算法了,另一個缺點是如果以後打分演算法要換回來就找不到程式碼,雖然SVN和GIT這種版本管理工具都有歷史提交記錄的功能,但還是顯得麻煩

(2)把原有的打分邏輯註釋,但這麼做的最大缺點是程式碼中有大量的註釋,尤其在策略邏輯非常長的時候,這就導致了程式碼的可讀性非常差

此時,就可以使用策略模式,將打分邏輯抽象為一種策略,換打分策略,新增一個策略的實現類,最後再讓程式碼中傳入新的策略實現類即可。

 

策略模式在Java中的應用及解讀

策略模式在Java中的應用,這個太明顯了,因為Comparator這個介面簡直就是為策略模式而生的。Comparable和Comparator的區別一文中,詳細講了Comparator的使用。比方說Collections裡面有一個sort方法,因為集合裡面的元素有可能是複合物件,複合物件並不像基本資料型別,可以根據大小排序,複合物件怎麼排序呢?基於這個問題考慮,Java要求如果定義的複合物件要有排序的功能,就自行實現Comparable介面或Comparator介面,看一下sort帶Comparator的過載方法:

1 public static <T> void sort(List<T> list, Comparator<? super T> c) {
2     Object[] a = list.toArray();
3     Arrays.sort(a, (Comparator)c);
4     ListIterator i = list.listIterator();
5     for (int j=0; j<a.length; j++) {
6         i.next();
7         i.set(a[j]);
8     }
9     }

跟一下第3行:

1 public static <T> void sort(T[] a, Comparator<? super T> c) {
2     T[] aux = (T[])a.clone();
3         if (c==null)
4             mergeSort(aux, a, 0, a.length, 0);
5         else
6             mergeSort(aux, a, 0, a.length, 0, c);
7     }

傳入的c不為null,跟一下第6行的mergeSort:

 1 private static void mergeSort(Object[] src,
 2                   Object[] dest,
 3                   int low, int high, int off,
 4                   Comparator c) {
 5     int length = high - low;
 6 
 7     // Insertion sort on smallest arrays
 8     if (length < INSERTIONSORT_THRESHOLD) {
 9         for (int i=low; i<high; i++)
10         for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
11             swap(dest, j, j-1);
12         return;
13     }
14 
15         // Recursively sort halves of dest into src
16         int destLow  = low;
17         int destHigh = high;
18         low  += off;
19         high += off;
20         int mid = (low + high) >>> 1;
21         mergeSort(dest, src, low, mid, -off, c);
22         mergeSort(dest, src, mid, high, -off, c);
23 
24         // If list is already sorted, just copy from src to dest.  This is an
25         // optimization that results in faster sorts for nearly ordered lists.
26         if (c.compare(src[mid-1], src[mid]) <= 0) {
27            System.arraycopy(src, low, dest, destLow, length);
28            return;
29         }
30 
31         // Merge sorted halves (now in src) into dest
32         for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
33             if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
34                 dest[i] = src[p++];
35             else
36                 dest[i] = src[q++];
37         }
38     }

第10行,根據Comparator介面實現類的compare方法的返回結果決定是否要swap(交換)。

這就是策略模式,我們可以給Collections的sort方法傳入不同的Comparator的實現類作為不同的比較策略。不同的比較策略,對同一個集合,可能會產生不同的排序結果。

 

認識策略模式

應當明白,策略模式的重心不是如何實現演算法(就如同工廠模式的重心不是工廠中如何產生具體子類一樣),而是如何組織、呼叫這些演算法,從而讓程式結構更靈活,具有更好的維護性和擴充套件性。

策略模式有一個很大的特點就是各策略演算法的平等性。對於一系列具體的策略演算法,大家的地位是完全一樣的,正因為這個平等性,各個演算法之間才可以相互替換。

執行期間,每一個時刻只能使用一個具體的策略實現物件,雖然可以動態地在不同的策略中實現切換。

 

策略模式的優缺點

優點

1、避免了多重條件if...else if...else語句,多重條件語句並不容易維護

2、策略模式提供了管理相關演算法簇的辦法,恰當使用繼承可以把公共程式碼移到父類,從而避免了程式碼重複

缺點

1、客戶端必須知道所有的策略類,並自行決定使用 哪一個策略,這意味著客戶端必須理解這些演算法的區別,以便選擇恰當的演算法

2、如果備選策略很多,物件的資料會很多

相關文章