Java 策略模式(決策者模式)實戰:從原理到落地

策略模式(Strategy Pattern)是一種行為型設計模式,核心思想是:定義一系列算法(策略),將每個算法封裝起來並使它們可互換,讓算法的變化獨立於使用算法的客户端。它解決了“算法切換”與“代碼耦合”的問題,讓代碼更易擴展、維護和測試。

本文將從“應用場景→核心原理→實戰案例→優缺點分析”逐步拆解,用 Java 實現一個貼近工業級的實戰案例。

一、先明確:什麼時候用策略模式?

當你的代碼滿足以下特徵時,優先考慮策略模式:

  1. 存在多種可替換的算法/邏輯(如支付方式:微信、支付寶、銀聯;排序算法:冒泡、快排、歸併);
  2. 客户端需要動態切換算法(如用户下單時選擇不同支付方式,後台無需修改核心邏輯);
  3. 避免用大量 if-else/switch 語句(否則新增算法時需修改原有代碼,違反“開閉原則”)。

典型應用場景

  • 支付系統:不同支付渠道(微信、支付寶、ApplePay)的支付邏輯;
  • 排序/搜索:不同數據規模下切換排序算法;
  • 折扣計算:電商平台的滿減、打折、優惠券等優惠策略;
  • 日誌框架:不同輸出目的地(控制枱、文件、數據庫)的日誌打印策略。

二、策略模式核心結構

策略模式包含 3 個核心角色,嚴格遵循“開閉原則”(對擴展開放,對修改關閉):

角色

職責

策略接口(Strategy)

定義所有策略的統一行為規範(抽象方法),客户端通過該接口調用具體策略。

具體策略(ConcreteStrategy)

實現策略接口,封裝具體的算法邏輯(如微信支付、支付寶支付的具體流程)。

上下文(Context)

持有策略接口的引用,為客户端提供統一的調用入口,負責策略的切換和執行。

結構示意圖

┌───────────────┐       ┌───────────────┐
│   Context     │       │  Strategy     │
├───────────────┤       ├───────────────┤
│ - strategy:   │◄─────►│ + execute():  │
│   Strategy    │       │   void        │
├───────────────┤       └───────────────┘
│ + setStrategy()│              ▲
│ + doAction():  │              │
│   void        │              │
└───────────────┘              │
                               │
                ┌──────────────┴──────────────┐
                │                             │
        ┌───────▼───────┐             ┌───────▼───────┐
        │ ConcreteStrategyA │         │ ConcreteStrategyB │
        ├───────────────┤             ├───────────────┤
        │ + execute():  │             │ + execute():  │
        │   void        │             │   void        │
        └───────────────┘             └───────────────┘

三、實戰案例:電商平台支付系統

需求背景

設計一個電商支付模塊,支持 3 種支付方式:微信支付、支付寶支付、銀聯支付。要求:

  1. 用户下單時可選擇任意支付方式;
  2. 後續新增支付方式(如 ApplePay)時,無需修改原有支付核心邏輯;
  3. 避免使用大量 if-else 判斷支付類型。

實現步驟

1. 定義策略接口(支付策略規範)

首先定義所有支付方式的統一接口,明確核心行為(發起支付):

/**
 * 支付策略接口(Strategy):定義支付的統一規範
 */
public interface PaymentStrategy {
    /**
     * 發起支付
     * @param amount 支付金額(元)
     * @param orderId 訂單號
     * @return 支付結果(成功/失敗信息)
     */
    String pay(double amount, String orderId);

    /**
     * 獲取支付方式名稱(如"微信支付")
     */
    String getPaymentName();
}
2. 實現具體策略(不同支付方式)

每種支付方式作為一個具體策略,實現 PaymentStrategy 接口,封裝自身的支付邏輯(實際場景中會調用第三方支付 SDK):

(1)微信支付策略
/**
 * 微信支付(ConcreteStrategy)
 */
public class WechatPayment implements PaymentStrategy {
    // 模擬微信支付SDK的AppID和密鑰(實際從配置文件讀取)
    private static final String APP_ID = "wx1234567890abcdef";
    private static final String APP_SECRET = "abcdef1234567890";

    @Override
    public String pay(double amount, String orderId) {
        // 模擬微信支付的核心邏輯:驗證簽名、調用微信支付接口、返回結果
        System.out.printf("【微信支付】訂單號:%s,金額:%.2f 元,正在調用微信支付接口...%n", orderId, amount);
        // 模擬支付成功(實際需處理接口返回結果)
        return String.format("支付成功!訂單號:%s,支付方式:微信支付,金額:%.2f 元", orderId, amount);
    }

    @Override
    public String getPaymentName() {
        return "微信支付";
    }
}
(2)支付寶支付策略
/**
 * 支付寶支付(ConcreteStrategy)
 */
public class AlipayPayment implements PaymentStrategy {
    private static final String ALIPAY_PID = "2088123456789012";
    private static final String ALIPAY_PRIVATE_KEY = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAN...";

    @Override
    public String pay(double amount, String orderId) {
        System.out.printf("【支付寶支付】訂單號:%s,金額:%.2f 元,正在調用支付寶支付接口...%n", orderId, amount);
        return String.format("支付成功!訂單號:%s,支付方式:支付寶支付,金額:%.2f 元", orderId, amount);
    }

    @Override
    public String getPaymentName() {
        return "支付寶支付";
    }
}
(3)銀聯支付策略
/**
 * 銀聯支付(ConcreteStrategy)
 */
public class UnionPayPayment implements PaymentStrategy {
    private static final String UNIONPAY_MERCHANT_ID = "6222081234567890";

    @Override
    public String pay(double amount, String orderId) {
        System.out.printf("【銀聯支付】訂單號:%s,金額:%.2f 元,正在調用銀聯支付接口...%n", orderId, amount);
        return String.format("支付成功!訂單號:%s,支付方式:銀聯支付,金額:%.2f 元", orderId, amount);
    }

    @Override
    public String getPaymentName() {
        return "銀聯支付";
    }
}
3. 實現上下文(支付上下文)

上下文類持有策略接口的引用,為客户端提供統一的支付入口,負責策略的切換和執行。同時可封裝公共邏輯(如參數校驗):

/**
 * 支付上下文(Context):統一支付入口,管理支付策略
 */
public class PaymentContext {
    // 持有支付策略接口的引用(面向接口編程,而非具體實現)
    private PaymentStrategy paymentStrategy;

    /**
     * 構造器注入策略(初始化時指定策略)
     */
    public PaymentContext(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    /**
     * 動態切換策略(支持支付過程中切換支付方式)
     */
    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
        System.out.printf("已切換支付方式為:%s%n", paymentStrategy.getPaymentName());
    }

    /**
     * 統一支付入口(封裝公共邏輯+調用具體策略)
     */
    public String executePayment(double amount, String orderId) {
        // 1. 公共參數校驗(無需在每個策略中重複寫)
        if (amount <= 0) {
            throw new IllegalArgumentException("支付金額必須大於0!");
        }
        if (orderId == null || orderId.trim().isEmpty()) {
            throw new IllegalArgumentException("訂單號不能為空!");
        }

        // 2. 調用具體策略的支付邏輯(多態調用,無需關心具體是哪種支付方式)
        return paymentStrategy.pay(amount, orderId);
    }
}
4. 客户端調用(模擬用户下單支付)

客户端無需直接依賴具體支付策略,只需通過上下文調用統一接口,按需切換策略:

/**
 * 客户端(電商訂單系統)
 */
public class Client {
    public static void main(String[] args) {
        // 1. 模擬訂單信息
        String orderId = "ORDER_20251128_001";
        double amount = 299.99;

        // 2. 初始化上下文,選擇微信支付(可通過配置/用户輸入動態選擇)
        PaymentContext paymentContext = new PaymentContext(new WechatPayment());
        // 執行支付
        String result1 = paymentContext.executePayment(amount, orderId);
        System.out.println("支付結果:" + result1);
        System.out.println("------------------------");

        // 3. 動態切換支付方式為支付寶
        paymentContext.setPaymentStrategy(new AlipayPayment());
        String result2 = paymentContext.executePayment(amount, orderId);
        System.out.println("支付結果:" + result2);
        System.out.println("------------------------");

        // 4. 動態切換支付方式為銀聯
        paymentContext.setPaymentStrategy(new UnionPayPayment());
        String result3 = paymentContext.executePayment(amount, orderId);
        System.out.println("支付結果:" + result3);
    }
}

運行結果

【微信支付】訂單號:ORDER_20251128_001,金額:299.99 元,正在調用微信支付接口...
支付結果:支付成功!訂單號:ORDER_20251128_001,支付方式:微信支付,金額:299.99 元
------------------------
已切換支付方式為:支付寶支付
【支付寶支付】訂單號:ORDER_20251128_001,金額:299.99 元,正在調用支付寶支付接口...
支付結果:支付成功!訂單號:ORDER_20251128_001,支付方式:支付寶支付,金額:299.99 元
------------------------
已切換支付方式為:銀聯支付
【銀聯支付】訂單號:ORDER_20251128_001,金額:299.99 元,正在調用銀聯支付接口...
支付結果:支付成功!訂單號:ORDER_20251128_001,支付方式:銀聯支付,金額:299.99 元

四、擴展:新增支付方式(驗證開閉原則)

如果需要新增“ApplePay 支付”,只需新增具體策略類,無需修改上下文和客户端代碼,完全符合“開閉原則”:

/**
 * 新增ApplePay支付(無需修改原有代碼)
 */
public class ApplePayPayment implements PaymentStrategy {
    private static final String APPLE_PAY_MERCHANT_ID = "merchant.com.example";

    @Override
    public String pay(double amount, String orderId) {
        System.out.printf("【ApplePay支付】訂單號:%s,金額:%.2f 元,正在調用ApplePay接口...%n", orderId, amount);
        return String.format("支付成功!訂單號:%s,支付方式:ApplePay支付,金額:%.2f 元", orderId, amount);
    }

    @Override
    public String getPaymentName() {
        return "ApplePay支付";
    }
}

客户端調用新增策略:

// 切換為ApplePay支付
paymentContext.setPaymentStrategy(new ApplePayPayment());
String result4 = paymentContext.executePayment(amount, orderId);
System.out.println("支付結果:" + result4);

運行結果(新增邏輯生效,原有代碼無改動):

已切換支付方式為:ApplePay支付
【ApplePay支付】訂單號:ORDER_20251128_001,金額:299.99 元,正在調用ApplePay接口...
支付結果:支付成功!訂單號:ORDER_20251128_001,支付方式:ApplePay支付,金額:299.99 元

五、進階優化:結合工廠模式+枚舉(減少客户端依賴)

上面的實現中,客户端需要手動創建具體策略對象(如 new WechatPayment()),如果策略較多,客户端會依賴所有具體策略類。可以結合工廠模式+枚舉優化,讓客户端通過“支付類型”動態獲取策略:

1. 定義支付類型枚舉

public enum PaymentType {
    WECHAT_PAY("微信支付"),
    ALIPAY("支付寶支付"),
    UNION_PAY("銀聯支付"),
    APPLE_PAY("ApplePay支付");

    private final String name;

    PaymentType(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

2. 實現策略工廠

/**
 * 支付策略工廠:根據支付類型創建具體策略對象
 */
public class PaymentStrategyFactory {
    // 緩存策略對象(單例,避免重複創建)
    private static final Map<PaymentType, PaymentStrategy> STRATEGY_MAP = new HashMap<>();

    // 靜態初始化:註冊所有策略
    static {
        STRATEGY_MAP.put(PaymentType.WECHAT_PAY, new WechatPayment());
        STRATEGY_MAP.put(PaymentType.ALIPAY, new AlipayPayment());
        STRATEGY_MAP.put(PaymentType.UNION_PAY, new UnionPayPayment());
        STRATEGY_MAP.put(PaymentType.APPLE_PAY, new ApplePayPayment());
    }

    /**
     * 根據支付類型獲取策略
     */
    public static PaymentStrategy getStrategy(PaymentType paymentType) {
        PaymentStrategy strategy = STRATEGY_MAP.get(paymentType);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付方式:" + paymentType.getName());
        }
        return strategy;
    }
}

3. 優化後客户端調用

public class OptimizedClient {
    public static void main(String[] args) {
        String orderId = "ORDER_20251128_002";
        double amount = 599.00;

        // 1. 從工廠獲取策略(客户端無需依賴具體策略類)
        PaymentStrategy wechatStrategy = PaymentStrategyFactory.getStrategy(PaymentType.WECHAT_PAY);
        PaymentContext context = new PaymentContext(wechatStrategy);
        System.out.println(context.executePayment(amount, orderId));

        // 2. 切換為支付寶(通過枚舉指定類型)
        PaymentStrategy alipayStrategy = PaymentStrategyFactory.getStrategy(PaymentType.ALIPAY);
        context.setPaymentStrategy(alipayStrategy);
        System.out.println(context.executePayment(amount, orderId));
    }
}

優化亮點

  • 客户端僅依賴 PaymentType 枚舉和工廠類,不直接依賴具體策略,降低耦合;
  • 策略對象緩存為單例,減少內存開銷;
  • 新增策略時,只需修改工廠類的靜態初始化塊,客户端無感知。

六、策略模式的優缺點

優點

  1. 符合開閉原則:新增策略無需修改原有代碼,只需新增具體策略類;
  2. 消除大量 if-else/switch:算法切換通過策略對象實現,代碼更簡潔易維護;
  3. 算法複用性高:每個策略封裝獨立邏輯,可在不同場景複用;
  4. 擴展性強:策略可動態切換,適配不同業務場景(如支付方式、優惠規則)。

缺點

  1. 策略類數量增多:每種算法對應一個策略類,若算法過多,會導致類數量膨脹;
  2. 客户端需瞭解策略:客户端需要知道不同策略的區別(如支付類型),才能選擇合適的策略;
  3. 增加了系統複雜度:相比簡單的 if-else,多了策略接口、上下文、工廠等類,入門成本略高。

七、使用場景總結

策略模式適用於“算法多樣化、需要動態切換”的場景,尤其適合:

  • 支付系統、優惠計算系統、日誌系統等;
  • 避免 if-else 超過 3 層的場景;
  • 算法需要被複用或擴展的場景。

核心口訣:策略封裝成接口,具體實現各不同,上下文來做調度,切換擴展無壓力。

如果需要進一步優化(如結合 Spring 依賴注入實現策略自動註冊、分佈式場景下的策略動態加載),可以隨時告訴我!