Java中的策略模式

地维藏光發表於2024-10-07

Java中的策略模式

綜述

本文總結了策略模式的定義,特點,使用場景以及實現思路。

策略模式的定義

策略模式說通了, 就是定義一系列的演算法, 將它們各自封裝起來, 並且使用一個共同的介面使它們可相互替換. 使得演算法和演算法之間沒有耦合, 這樣如果方法需要修改或者新增, 工程師不需要修改那些無關的演算法. 特別是當業務邏輯需要從多種演算法之中挑選自己需要的演算法時, 採用策略模式會非常的有效.

策略模式的特點和使用場合

策略模式主要使用場景在於將業務程式碼和具體演算法進行解耦. 當某個業務程式碼需要用到某種演算法來實現這個功能, 且不能保證未來一直永這類演算法, 並共用同一個介面, 這時候就可以考慮策略模式. 常見的使用場景包括排序演算法、快取策略、支付方式等等

策略模式有以下優點:

  • 對演算法或行為進行封裝, 方便開發和理解.
  • 切換方便, 擴充套件也方便.

當然策略模式也有缺點, 策略模式為了將演算法和業務模式解耦, 額外新增了類的數量以及程式碼量. 所以在演算法數量較少的時候(特別是乾脆只有一種演算法的時候)不建議採用策略模式, 避免出現殺雞用牛刀空耗時間與精力.

策略模式的實現思路

策略模式包含以下幾個角色:

  • 上下文(Context):維護一個對策略物件的引用,負責將客戶端請求委派給具體的策略物件執行。上下文類可以透過依賴注入、簡單工廠等方式來獲取具體策略物件。
  • 抽象策略(Abstract Strategy):定義了策略物件的公共介面或抽象類,規定了具體策略類必須實現的方法。
  • 具體策略(Concrete Strategy):實現了抽象策略定義的介面或抽象類,包含了具體的演算法實現。

如果讀者熟悉狀態模式的話就會發現二者非常的相似, 因為狀態模式的核心角色分別是 上下文 抽象狀態和具體狀態. 可以發現這兩個模式在很多地方有類似之處, 這也是為什麼很多開發者會選擇用策略模式替代狀態模式的原因. 但是兩者仍然存在一些本質上的不同.
對於狀態模式而言, 狀態的切換應該是固定的, 切換順序根據不同的實現思路應該內建於具體狀態或者上下文之中. 而策略模式中不同的狀態切換是外接的, 是可以交由context的例項的呼叫者根據需要以任意切換的.

策略模式示例

下面展示一個有足夠泛用性示例, 策略模式同時還結合了工廠模式, 實際使用的時候還可以結合單例模式但是此處就不再繼續擴充了

首先是 AbstractStrategy 介面, 這裡內部的方法叫什麼不重要, 重要的是所有策略都需要實現同樣的方法, 既所有的策略必須接收同樣的引數, 或者不接收任何引數, 當然如果不同的策略實際用到的引數不一樣, 可以使用最大集構建一個接收所有引數的execute(para...) 方法, 這樣用到哪個引數就呼叫哪個引數, 用不到就忽略

public interface AbstractStrategy {
    void execute();
}

然後是 AbstractStrategy 的各種實現類, 既單獨的策略

public class FooBarConcreteStrategy implements AbstractStrategy{

    @Override
    public void execute() {
        //在真實的使用時可以在方法中進行任意操作, 
        //此處只使用列印文字作為舉例, 下同
        System.out.println("foobar");
    }
}

public class HelloWorldConcreteStrategy implements AbstractStrategy{

    @Override
    public void execute() {
        System.out.println("hello, world");
    }
}

然後是context類

public class ExecuteContext {
    private AbstractStrategy strategy;

    // 設定策略
    public void setStrategy(AbstractStrategy strategy) {
        this.strategy = strategy;
    }

    // 執行策略
    public void executeStrategy() {
        strategy.execute();
        return;
    }
}

最後是一個呼叫的示例, 實際使用的時候當然要放在具體的方法中

public class StrategyPattern {
    public static void main(String[] args){
        ExecuteContext context = new ExecuteContext();
        context.setStrategy(new HelloWorldConcreteStrategy());
        context.executeStrategy();
    }
}

策略模式擴充版本

因為純策略模式要求呼叫者熟悉當前需要測策略, 在易用性方面不夠友好, 所以這裡給出一個策略模式+簡單工廠模式的擴充版本

相較於原來的策略模式, 修改context, 這裡context同時還承擔了factory的職責, 所以核心是STRATEGY_MAP, 用於對外返回需要的策略例項. 同時內建一個public型別的內部列舉類, 這樣外部呼叫策略例項的時候可以透過列舉類的提示知道當前提供哪幾種策略. 具體如下

public class ExecuteContextExtension {

    private static final Map<ExecuteStrategyName, AbstractStrategy> STRATEGY_MAP = new HashMap<>();

    static {
        STRATEGY_MAP.put(ExecuteStrategyName.HelloWorld, new HelloWorldConcreteStrategy());
        STRATEGY_MAP.put(ExecuteStrategyName.FooBar, new FooBarConcreteStrategy());
    }

    public AbstractStrategy getStrategy(ExecuteStrategyName name){
        return STRATEGY_MAP.get(name);
    }

    public enum ExecuteStrategyName {
        HelloWorld,
        FooBar,
    }
}

呼叫的寫法為

public class StrategyPattern {
    public static void main(String[] args){
        ExecuteContextExtension factory = new ExecuteContextExtension();
        AbstractStrategy foobarStrategy = factory.getStrategy(ExecuteStrategyName.FooBar);

        foobarStrategy.execute();
}

其餘不變
這一擴充的好處在於, 對於呼叫者而言它可以直接從context內建的列舉類中知道整個模組提供了哪幾種策略. 易用性拉滿, 當然缺點是對開閉原則的支援沒有純策略模式那麼好.

總結

策略模式是23種設計模式中的一種, 屬於行為模式這一細分類別, 主要用於解耦業務邏輯和具體演算法. 個人以為, 策略模式屬於和觀察者模式以及工廠模式同一級別的標杆級設計模式, 構思精妙, 威力強大, 應用廣泛. 當然和單例模式這種簡單易行的模式不同, 也就沒有速成之法, 新手從接觸到上手總還是要半天時間.

相關文章