1. 程式人生 > 實用技巧 >設計模式——策略模式

設計模式——策略模式

策略模式

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

策略模式的結構

這個模式涉及到三個角色:

  • 環境(Context)角色:持有一個 Strategy 類的引用
  • 抽象策略(Strategy)角色:這是一個抽象角色,通常由一個介面或抽象類實現。此角色給出所有的具體策略類所需的介面
  • 具體策略(ConcreteStrategy)角色:包裝了相關的演算法或行為。

原始碼:

Context:

public class Context {
    private Strategy strategy;

    /**
    * 策略方法
    */
    public void contextInterface(){
        strategy.strategyInterface();
    }
}

Strategy: 這個抽象類也可以用 Java 介面來取代。

abstract public class Strategy {
    /**
    * 策略方法
    */
    public abstract void strategyInterface();
}

ConcreteStrategyA:

public class ConcreteStrategyA extends Strategy {
    /**
    * 策略方法
    */
    public void strategyInterface(){
        // 具體業務程式碼
    }
}

ConcreteStrategyB:

public class ConcreteStrategyB extends Strategy {
    /**
    * 策略方法
    */
    public void strategyInterface(){
        // 具體業務程式碼
    }
}

ConcreteStrategyC:

public class ConcreteStrategyC extends Strategy {
    /**
    * 策略方法
    */
    public void strategyInterface(){
        // 具體業務程式碼
    }
}

例子

假設現在要設計一個販賣各類書籍的電子商務網站的購物車系統。一個最簡單的情況就是把所有貨品的單價乘上數量,但是實際情況肯定比這要複雜。比如,本網站可能對所有的高階會員提供每本20%的促銷折扣;對中級會員提供每本10%的促銷折扣;對初級會員沒有折扣。

  根據描述,折扣是根據以下的幾個演算法中的一個進行的:

  演算法一:對初級會員沒有折扣。

  演算法二:對中級會員提供10%的促銷折扣。

  演算法三:對高階會員提供20%的促銷折扣。

抽象折扣類:

public interface MemberStrategy {
    /**
     * 計算圖書的價格
     * @param booksPrice    圖書的原價
     * @return    計算出打折後的價格
     */
    public double calcPrice(double booksPrice);
}

初級會員折扣類:

public class PrimaryMemberStrategy implements MemberStrategy {

    @Override
    public double calcPrice(double booksPrice) {
        
        System.out.println("對於初級會員的沒有折扣");
        return booksPrice;
    }

}

中級會員折扣類:

public class IntermediateMemberStrategy implements MemberStrategy {

    @Override
    public double calcPrice(double booksPrice) {

        System.out.println("對於中級會員的折扣為10%");
        return booksPrice * 0.9;
    }

}

高階會員折扣類:

public class AdvancedMemberStrategy implements MemberStrategy {

    @Override
    public double calcPrice(double booksPrice) {
        
        System.out.println("對於高階會員的折扣為20%");
        return booksPrice * 0.8;
    }
}

價格類:

public class Price {
    //持有一個具體的策略物件
    private MemberStrategy strategy;
    /**
     * 建構函式,傳入一個具體的策略物件
     * @param strategy    具體的策略物件
     */
    public Price(MemberStrategy strategy){
        this.strategy = strategy;
    }
    
    /**
     * 計算圖書的價格
     * @param booksPrice    圖書的原價
     * @return    計算出打折後的價格
     */
    public double quote(double booksPrice){
        return this.strategy.calcPrice(booksPrice);
    }
}

客戶端:

public class Client {

    public static void main(String[] args) {
        //選擇並建立需要使用的策略物件
        MemberStrategy strategy = new AdvancedMemberStrategy();
        //建立環境
        Price price = new Price(strategy);
        //計算價格
        double quote = price.quote(300);
        System.out.println("圖書的最終價格為:" + quote);
    }

}

策略模式優點:

  1. 策略模式提供了管理相關的演算法族的辦法。策略類的等級結構定義了一個演算法或行為族。恰當使用繼承可以把公共的程式碼移到父類裡面,從而避免程式碼重複。
  2. 策略模式提供了可以替換繼承關係的辦法。繼承可以處理多種演算法或行為。如果不是用策略模式,那麼使用演算法或行為的環境類就可能會有一些子類,每一個子類提供一個不同的演算法或行為。但是,這樣一來演算法或行為的使用者就和演算法或行為本身混在一起。決定使用哪一種演算法或採取哪一種行為的邏輯就和演算法或行為的邏輯混在一起而不可能再獨立演化。繼承使得動態改變演算法或行為變得不可能。
  3. 使用策略模式可以避免使用多重條件轉移語句。多重條件轉移語句不易維護,它把採取哪一種演算法或採取哪一種行為的邏輯與演算法或行為的邏輯混合在一起,統統羅列在一個多重轉移語句裡面,比使用繼承的辦法還要原始和落後。

策略模式的缺點:

  1. 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味著客戶端必須理解這些演算法的區別以便適時選擇恰當的演算法類。換言之,策略模式只適用於客戶端知道所有的演算法或行為的情況。
  2. 策略模式造成很多策略類。有時候可以通過把依賴於環境的狀態儲存到客戶端裡面而將策略類設計成可共享的,這樣策略類例項可以被不同客戶端使用。換言之,可以使用享元模式來減少物件的數量。

參考:

  1. https://www.cnblogs.com/java-my-life/archive/2012/05/10/2491891.html
  2. 《Java 與模式》 閻巨集
  3. 《設計模式——可複用面向物件軟體的基礎》Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides