首頁>技術>

前言

之前做三方支付系統的時候經常用到策略模式,比如使用者會選擇不同的支付方式,不同的支付方式又有不同的實現方法或銀行介面呼叫。

現在做物聯網系統,基於MQTT協議(TCP層面的協議)來傳輸資料,根據不同的請求(不同的Topic)處理不同的業務邏輯,也同樣用到策略模式。

頓時感覺策略模式非常好用,而且結合Spring的例項化和注入功能,更加方便了。

今天就聊聊基於Spring(Boot)下策略模式的使用。

最簡單直接的程式碼實現形式如下:

public void pay(String payType){    if("alipay".equals(payType)){        System.out.println("支付寶");    }else if("wechatPay".equals(payType)){        System.out.println("微信支付");    } else if("bank".equals(payType)){        System.out.println("銀行卡支付");    }}

這樣對照設計模式,通常不符合兩個原則:單一職責原則和開閉原則。

我們會發現當前類(或方法)不處理了多個業務的功能,一旦任何一個支付方式的修改都可能會影響到其他的支付方式。同時,無法做到對擴充套件開放,對修改關閉。新增其他支付方式時同樣要修改ifelse判斷,影響到其他的業務邏輯。

而策略模式通常就是解決這種有很多ifelse處理邏輯,從而提高程式碼的可維護性、可擴充套件性和可讀性。

策略模式的輪廓

在對上述程式碼進行改造之前,先來了解一下策略模式的基本組成。

策略模式(Strategy),定義了一組演算法,將每個演算法都封裝起來,並且使它們之間可以互換。

策略模式通常由以下幾部分組成:

Strategy策略類,用於定義所有支援演算法的公共介面;ConcreteStrategy具體策略類,封裝了具體的演算法或行為,繼承於Strategy。Context上下文,用一個ConcreteStrategy來配置,維護一個對Strategy物件的引用;StrategyFactory策略工廠類,用於建立策略類的具體實現;通常此部分可省略,看具體情況。比如後續例項中透過Spring的依賴注入機制實現了策略類的例項化。

用類圖來表示(省略策略工廠類)如下圖:

基於Spring的策略模式實現

目前在實踐中通常都是基於Spring的特性來實現策略模式,這裡就以此為例來進行講解。

策略類定義

上面已經提到,策略類用於定義功能的介面,對於支付場景則可命名為PaymentService或PaymentStrategy。

public interface PaymentService {    /**     * 支付     */    PayResult pay(Order order);}

同時提供該策略類的不同實現類:AlipayService、WeChatPayService、BankPayService。

@Service("alipay")public class AlipayService implements PaymentService {    @Override    public PayResult pay(Order order) {        System.out.println("Alipay");        return null;    }}
@Service("wechatPay")public class WeChatPayService implements PaymentService {    @Override    public PayResult pay(Order order) {        System.out.println("WeChatPay");        return null;    }}
@Service("bank")public class BankPayService implements PaymentService {    @Override    public PayResult pay(Order order) {        System.out.println("BankPay");        return null;    }}

具體實現的例項化,可以透過一個PaymentFactory來進行構建儲存,也可以直接利用@Autowired形式注入到Context的List或Map當中。

PaymentFactory的實現如下:

public class PaymentFactory {    private static final Map<String, PaymentService> payStrategies = new HashMap<>();    static {        payStrategies.put("alipay", new AlipayService());        payStrategies.put("wechatPay", new WeChatPayService());        payStrategies.put("bank", new BankPayService());    }    public static PaymentService getPayment(String 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);    }}

透過static靜態程式碼塊來初始化對應的策略實現類,然後提供一個getPayment方法,根據支付型別來獲取對應的服務。當然,透過static初始化的程式碼塊是單例的無狀態的,如果需要有狀態的類則getPayment方法,每次都需要new一個新的物件。

public static PaymentService getPayment1(String payType) {    if (payType == null) {        throw new IllegalArgumentException("pay type is empty.");    }    if ("alipay".equals(payType)) {        return new AlipayService();    } else if ("wechatPay".equals(payType)) {        return new WeChatPayService();    } else if ("bank".equals(payType)) {        return new BankPayService();    }    throw new IllegalArgumentException("pay type not supported.");}
Context上下文

Context上下文角色,也叫Context封裝角色,起承上啟下的作用,遮蔽高層模組對策略、演算法的直接訪問,封裝可能存在的變化。

上面透過工廠的形式建立策略類的實現類,當然也可以直接透過@Autowired注入到Context上下文中。

@Componentpublic class PaymentStrategy {    @Autowired    private final Map<String, PaymentService> payStrategies = new HashMap<>();    public PaymentService getPayment(String 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);    }}

上面透過@Autowired註解,將透過@Service例項化的PaymentService實現類,注入到map當中,其中key為例項化類的名稱,value為具體的例項化類。

上面的getPayment程式碼與PaymentFactory中一致。當然,還可以在PaymentStrategy中封裝一個pay方法,這樣,客戶端直接注入PaymentStrategy類呼叫pay方法即可。

public PayResult pay(String payType,Order order){    PaymentService paymentService = this.getPayment(payType);    return paymentService.pay(order);}
改進方案

透過上面的程式碼基本上已經實現了策略模式,此時當新增加一個支付通道時,已經不用修改PaymentStrategy相關的程式碼,只用新增一個實現PaymentService介面的類即可。

但在介面定義這裡,還是有最佳化空間的。比如,這裡判斷是透過Bean的名稱來判斷的,但某些情況下判斷可能比較複雜或可能會同時執行多個Service。此時,就可以對PaymentService介面進行改進,新增一個檢驗是否支援該功能的判斷方法。

public interface PaymentService {    boolean isSupport(Order order);    /**     * 支付     */    PayResult pay(Order order);}

由實現類來具體實現isSupport方法,判斷自己支援哪些功能。

同時,上下文類也可以進一步利用Java8提供的Steam特性進行處理:

@Componentpublic class PaymentStrategy {    /**     * 此處用@Autowired將所有例項注入為List。     */    @Autowired    private List<PaymentService> paymentServices;    public void pay(Order order) {        PaymentService paymentService = paymentServices.stream()                .filter((service) -> service.isSupport(order))                .findFirst()                .orElse(null);        if (paymentService != null) {            paymentService.pay(order);        } else {            throw new IllegalArgumentException("pay type not supported.");        }    }}

透過進一步改造,程式變得更加靈活了。

小結

透過上面的程式碼實現,可以看出介面類只負責業務策略的定義,策略的具體實現可以單獨放在實現類中也可以利用Spring的特性進行管理,Context上下文類負責業務邏輯的編排。

透過策略模式(或變種)的應用,實現了面向介面而非實現程式設計,滿足了職責單一、開閉原則,從而達到了功能上的高內聚低耦合、提高了可維護性、擴充套件性以及程式碼的可讀性。

最後,對於設計模式,只有在實踐中不斷的使用採用更加印象深刻。同時,在實現的過程中我們也並不一定非要拘泥於設計模式本身,也可以結合所使用的框架進行變種處理。

9
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • LinuxSSH埠轉發實踐