策略模式詳解

圖片

策略模式(Strategy Pattern)定義了一組同類型的算法,在不一樣的類中封裝起來,每種算法能夠根據當前場景相互替換,從而使算法的變化獨立於使用它們的客戶端(即算法的調用者)。《GoF 設計模式》書中,它是這樣定義的:算法

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

例如:在網購中,我在支付的時候,能夠根據實際狀況來選擇不一樣的支付方式(微信支付、支付寶、銀行卡支付等等),這些支付方式便是不一樣的策略。咱們一般會看到以下的實現代碼:編程

Order order = 訂單信息
if (payType == 微信支付) {
   微信支付流程
} else if (payType == 支付寶) {
   支付寶支付流程
} else if (payType == 銀行卡) {
   銀行卡支付流程
} else {
   暫不支持的支付方式
}

如上代碼,雖然寫起來簡單,但違反了面向對象的 2 個基本原則:設計模式

  • 單一職責原則:一個類只有1個發生變化的緣由緩存

    以後修改任何邏輯,當前方法都會被修改微信

  • 開閉原則:對擴展開放,對修改關閉app

    當咱們須要增長、減小某種支付方式(積分支付/組合支付),或者增長優惠券等功能時,不可避免的要修改該段代碼less

特別是當 if-else 塊中的代碼量比較大時,後續的擴展和維護會變得很是複雜且容易出錯。在阿里《Java開發手冊》中,有這樣的規則:超過3層的 if-else 的邏輯判斷代碼能夠使用衛語句、策略模式、狀態模式等來實現ide

策略模式是解決過多 if-else(或者 switch-case) 代碼塊的方法之一,提升代碼的可維護性、可擴展性和可讀性。下面我將從策略的定義、建立和使用這三個方面以上述網購支付爲示例來分別進行說明。微信支付

1. 策略的定義

策略接口的定義,一般包含兩個方法:獲取策略類型的方法和處理策略業務邏輯的方法。ui

/**
* 第三方支付
*/

public interface Payment {

   /**
    * 獲取支付方式
    *
    * @return 響應,支付方式
    */

   PayTypeEnum getPayType();

   /**
    * 支付調用
    *
    * @param order 訂單信息
    * @return 響應,支付結果
    */

   PayResult pay(Order order);

}

策略接口的實現,每種支付類都實現了上述接口(基於接口而非實現編程),這樣咱們能夠靈活的替換不一樣的支付方式。下邊示例代碼展現了每種支付方式的實現:

/**
* 微信支付
*/

@Component
public class WxPayment implements Payment {

   @Override
   public PayTypeEnum getPayType() {
       return PayTypeEnum.WX;
   }

   @Override
   public PayResult pay(Order order) {
       調用微信支付
       if (成功) {
           return PayResult.SUCCESS;
       } else {
           return PayResult.FAIL;
       }
   }

}
/**
* 支付寶支付
*/

@Component
public class AlipayPayment implements Payment {

   @Override
   public PayTypeEnum getPayType() {
       return PayTypeEnum.ALIPAY;
   }

   @Override
   public PayResult pay(Order order) {
       調用支付寶支付
       if (成功) {
           return PayResult.SUCCESS;
       } else {
           return PayResult.FAIL;
       }
   }

}
/**
* 銀行卡支付
*/

@Component
public class BankCardPayment implements Payment {

   @Override
   public PayTypeEnum getPayType() {
       return PayTypeEnum.BANK_CARD;
   }

   @Override
   public PayResult pay(Order order) {
       調用銀行卡支付
       if (成功) {
           return PayResult.SUCCESS;
       } else {
           return PayResult.FAIL;
       }
   }

}

2. 策略的建立

策略模式包含一組同類的策略,在使用時咱們一般經過類型來判斷建立哪一種策略來進行使用。咱們能夠使用工廠模式來建立策略,以屏蔽策略的建立細節。以下代碼所示:

public class PaymentFactory {
   private static final Map<PayTypeEnum, Payment> payStrategies = new HashMap<>();

   static {
       payStrategies.put(PayTypeEnum.WX, new WxPayment());
       payStrategies.put(PayTypeEnum.ALIPAY, new AlipayPayment());
       payStrategies.put(PayTypeEnum.BANK_CARD, new BankCardPayment());
   }

   public static Payment getPayment(PayTypeEnum payType) {
       if (payType == null) {
           throw new IllegalArgumentException("pay type is empty.");
       }
       if (!payStrategies.containsKey(payType)) {
           throw new IllegalArgumentException("pay type not supported.");
       }
       return payStrategies.get(payType);
   }

}

或者使用 Spring 建立:

@Component
public class PaymentFactory implements InitializingBean, ApplicationContextAware {
   private static final Map<PayTypeEnum, Payment> payStrategies = new HashMap<>();

   private ApplicationContext appContext;

   public static Payment getPayment(PayTypeEnum payType) {
       if (payType == null) {
           throw new IllegalArgumentException("pay type is empty.");
       }
       if (!payStrategies.containsKey(payType)) {
           throw new IllegalArgumentException("pay type not supported.");
       }
       return payStrategies.get(payType);
   }

   @Override
   public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
       appContext = applicationContext;
   }

   @Override
   public void afterPropertiesSet() {
       // 將 Spring 容器中全部的 Payment 接口實現類註冊到 payStrategies
       appContext.getBeansOfType(Payment.class)
                 .values()
                 .forEach(payment -> payStrategies.put(payment.getPayType(), payment))
;
   }
}

注意:以上兩種建立方式,都是無狀態的,即不包含成員變量,它們能夠被共享使用。若是策略類是有狀態的,須要根據業務場景每次建立新的策略對象,那麼咱們能夠在工廠方法中,每次生成新的策略對象,而不是使用已經提早緩存好的策略對象。以下代碼所示:

public class PaymentFactory {
   public static Payment getPayment(PayTypeEnum payType) {
       if (payType == null) {
           throw new IllegalArgumentException("pay type is empty.");
       }
       if (payType == PayTypeEnum.WX) {
           return new WxPayment();
       }
       if (payType == PayTypeEnum.ALIPAY) {
           return new AlipayPayment();
       }
       if (payType == PayTypeEnum.BANK_CARD) {
           return new BankCardPayment();
       }
       throw new IllegalArgumentException("pay type not supported.");
   }

}

3. 策略的使用

一般咱們事先並不知道會使用哪一個策略,在程序運行時根據配置、用戶輸入、計算結果等來決定到底使用哪一種策略。例如,前邊支付方式的例子,咱們會根據用戶的選擇來決定使用哪一種支付方式。使用策略模式的代碼實現以下:

Order order = 訂單信息
PayResult payResult = PaymentFactory.getPayment(payType).pay(order);
if (payResult == PayResult.SUCCESS) {
   System.out.println("支付成功");
} else if (payType == 支付寶) {
   System.out.println("支付失敗");
}

綜上代碼中,接口類只負責業務策略的定義,每一個策略的具體實現單獨放在實現類中,工廠類 Factory 只負責獲取具體實現類,而具體調用代碼則負責業務邏輯的編排。這些實現用到了面向接口而非實現編程,知足了職責單1、開閉原則,從而達到了功能上的高內聚低耦合、提升了可維護性、擴展性以及代碼的可讀性。

另外,可關注個人公衆號或者QQ羣(781374180),2021年我將系統化的分享 Java 技術棧相關內容,包括但不限於:Java 基礎、MyBatis、MySQL、Spring 全家桶、MQ、Redis、ES、雲原生、ServiceMesh、Serverless等。

相關文章
相關標籤/搜索