再一次實戰(zhàn)策略模式,真是太好用了
本文轉(zhuǎn)載自微信公眾號「程序新視界」,作者二師兄。轉(zhuǎn)載本文請聯(lián)系程序新視界公眾號。
前言
之前做三方支付系統(tǒng)的時候經(jīng)常用到策略模式,比如用戶會選擇不同的支付方式,不同的支付方式又有不同的實現(xiàn)方法或銀行接口調(diào)用。
現(xiàn)在做物聯(lián)網(wǎng)系統(tǒng),基于MQTT協(xié)議(TCP層面的協(xié)議)來傳輸數(shù)據(jù),根據(jù)不同的請求(不同的Topic)處理不同的業(yè)務邏輯,也同樣用到策略模式。
頓時感覺策略模式非常好用,而且結(jié)合Spring的實例化和注入功能,更加方便了。
今天就聊聊基于Spring(Boot)下策略模式的使用。
未使用策略模式時的處理
以物聯(lián)網(wǎng)為例大家可能不夠熟悉,下面就以支付場景為例。比如在支付的過程中我們可能會選擇微信支付、支付寶支付或銀卡支付。同時,銀行卡又分不同銀行,這里統(tǒng)一為銀行卡。
最簡單直接的代碼實現(xiàn)形式如下:
- 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("銀行卡支付");
- }
- }
這樣對照設計模式,通常不符合兩個原則:單一職責原則和開閉原則。
我們會發(fā)現(xiàn)當前類(或方法)不處理了多個業(yè)務的功能,一旦任何一個支付方式的修改都可能會影響到其他的支付方式。同時,無法做到對擴展開放,對修改關(guān)閉。新增其他支付方式時同樣要修改ifelse判斷,影響到其他的業(yè)務邏輯。
而策略模式通常就是解決這種有很多ifelse處理邏輯,從而提高代碼的可維護性、可擴展性和可讀性。
策略模式的輪廓
在對上述代碼進行改造之前,先來了解一下策略模式的基本組成。
策略模式(Strategy),定義了一組算法,將每個算法都封裝起來,并且使它們之間可以互換。
策略模式通常由以下幾部分組成:
- Strategy策略類,用于定義所有支持算法的公共接口;
- ConcreteStrategy具體策略類,封裝了具體的算法或行為,繼承于Strategy。
- Context上下文,用一個ConcreteStrategy來配置,維護一個對Strategy對象的引用;
- StrategyFactory策略工廠類,用于創(chuàng)建策略類的具體實現(xiàn);通常此部分可省略,看具體情況。比如后續(xù)實例中通過Spring的依賴注入機制實現(xiàn)了策略類的實例化。
用類圖來表示(省略策略工廠類)如下圖:
image
基于Spring的策略模式實現(xiàn)
目前在實踐中通常都是基于Spring的特性來實現(xiàn)策略模式,這里就以此為例來進行講解。
策略類定義
上面已經(jīng)提到,策略類用于定義功能的接口,對于支付場景則可命名為PaymentService或PaymentStrategy。
- public interface PaymentService {
- /**
- * 支付
- */
- PayResult pay(Order order);
- }
同時提供該策略類的不同實現(xiàn)類: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;
- }
- }
具體實現(xiàn)的實例化,可以通過一個PaymentFactory來進行構(gòu)建存儲,也可以直接利用@Autowired形式注入到Context的List或Map當中。
PaymentFactory的實現(xiàn)如下:
- 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靜態(tài)代碼塊來初始化對應的策略實現(xiàn)類,然后提供一個getPayment方法,根據(jù)支付類型來獲取對應的服務。當然,通過static初始化的代碼塊是單例的無狀態(tài)的,如果需要有狀態(tài)的類則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封裝角色,起承上啟下的作用,屏蔽高層模塊對策略、算法的直接訪問,封裝可能存在的變化。
上面通過工廠的形式創(chuàng)建策略類的實現(xiàn)類,當然也可以直接通過@Autowired注入到Context上下文中。
- @Component
- public 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實現(xiàn)類,注入到map當中,其中key為實例化類的名稱,value為具體的實例化類。
上面的getPayment代碼與PaymentFactory中一致。當然,還可以在PaymentStrategy中封裝一個pay方法,這樣,客戶端直接注入PaymentStrategy類調(diào)用pay方法即可。
- public PayResult pay(String payType,Order order){
- PaymentService paymentService = this.getPayment(payType);
- return paymentService.pay(order);
- }
改進方案
通過上面的代碼基本上已經(jīng)實現(xiàn)了策略模式,此時當新增加一個支付通道時,已經(jīng)不用修改PaymentStrategy相關(guān)的代碼,只用新增一個實現(xiàn)PaymentService接口的類即可。
但在接口定義這里,還是有優(yōu)化空間的。比如,這里判斷是通過Bean的名稱來判斷的,但某些情況下判斷可能比較復雜或可能會同時執(zhí)行多個Service。此時,就可以對PaymentService接口進行改進,新增一個檢驗是否支持該功能的判斷方法。
- public interface PaymentService {
- boolean isSupport(Order order);
- /**
- * 支付
- */
- PayResult pay(Order order);
- }
由實現(xiàn)類來具體實現(xiàn)isSupport方法,判斷自己支持哪些功能。
同時,上下文類也可以進一步利用Java8提供的Steam特性進行處理:
- @Component
- public 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.");
- }
- }
- }
通過進一步改造,程序變得更加靈活了。
小結(jié)
通過上面的代碼實現(xiàn),可以看出接口類只負責業(yè)務策略的定義,策略的具體實現(xiàn)可以單獨放在實現(xiàn)類中也可以利用Spring的特性進行管理,Context上下文類負責業(yè)務邏輯的編排。
通過策略模式(或變種)的應用,實現(xiàn)了面向接口而非實現(xiàn)編程,滿足了職責單一、開閉原則,從而達到了功能上的高內(nèi)聚低耦合、提高了可維護性、擴展性以及代碼的可讀性。
最后,對于設計模式,只有在實踐中不斷的使用采用更加印象深刻。同時,在實現(xiàn)的過程中我們也并不一定非要拘泥于設計模式本身,也可以結(jié)合所使用的框架進行變種處理。