如何在Spring Boot項目中巧妙利用策略模式幹掉if else!

  • 2019 年 11 月 3 日
  • 筆記

直入主題

我們都知道,設計模式(Design Pattern)是前輩們對程式碼開發經驗的總結,是解決特定問題的一系列套路。它不是語法規定,而是一套用來提高程式碼可復用性、可維護性、可讀性、穩健性以及安全性的解決方案。
那麼,我們可能都了解過設計模式,但是在項目中怎麼使用可能還是會有點疑惑,今天,公司的項目剛好有一個場景來讓我使用一個設計模式:策略模式。

場景

關於用戶訂單充值(訂單支付同理),我們都知道,現今的支付方式是非常的多的,例如:支付寶、微信、銀聯、錢包(各個APP的賬戶餘額)等等。
查詢實體Query:

/**   * @author Howinfun   * @desc 查詢   * @date 2019/10/22   */  @Data  public class ChargeQuery {        /** 支付方式(ALI/WX/UNION) */      @NotBlank(message = "支付方式不能為空",groups = PayWayNotBlank.class)      private String payWay;        /** 充值金額 */      @NotNull(message = "充值金額不能為空",groups = AmountNotNull.class)      private Double amount;  }

Service介面:

/**   * @author Howinfun   * @desc 充電-充值模組   * @date 2019/10/30   */  public interface ChargeRechargeService {        /**       * 根據不用支付方式進行用戶餘額充值       * @param query       * @return       */      Result recharge(ChargeQuery query);        /**       * 充值回調       * @param rechargeCallBack       */      Result rechargeCallBack(RechargeCallBack rechargeCallBack);  }

傳統方式實現

就是利用if else或者switch來進行條件判斷:

/**   * @author Howinfun   * @desc   * @date 2019/10/30   */  @Service  @AllArgsConstructor  @Slf4j  public class ChargeRechargeServiceImpl implements ChargeRechargeService {        private final CarUserMapper carUserMapper;      private final IncomePaymentMapper incomePaymentMapper;      private final RechargeRecordMapper rechargeRecordMapper;      private final PayWayHandlerContext payWayHandlerContext;          @Override      @Transactional(rollbackFor = Exception.class)      public Result recharge(ChargeQuery query) {          Result result = new Result();          // ......          // ......          if (PayConstant.PAY_WAY_WX.equals(query.getPayWay())){              // 微信              // ......          }else if (PayConstant.PAY_WAY_ALI.equals(query.getPayWay())){              // 支付寶              // ......            }else if (PayConstant.PAY_WAY_UNION_PAY.equals(query.getPayWay())){               // 銀聯               // ......            }          return result;      }  }

總結:我們可以看到,傳統的實現方式是非常的笨重的,而且程式碼非常的不簡潔,擴展性差。假如我們要接入新的支付方式,那麼我們只能繼續添加 else if。

策略模式

Talk is cheap,show me the code.
我們先看一下,如果使用策略模式,service的程式碼將變成啥樣。

/**   * @author Howinfun   * @desc   * @date 2019/10/30   */  @Service  @AllArgsConstructor  @Slf4j  public class ChargeRechargeServiceImpl implements ChargeRechargeService {        private final PayWayHandlerContext payWayHandlerContext;          @Override      @Transactional(rollbackFor = Exception.class)      public Result recharge(ChargeQuery query) {          return this.payWayHandlerContext.getHandlerInstance(query.getPayWay()).handler(query);      }  }

emmmm,確實是簡單了不少。不但程式碼量少了,簡潔了,而且不再擔心因為新增支付方式而修改serviceImpl的程式碼了。

下面進行詳細的講解:
1、首先,我們需要自定義一個註解,來標識一個支付類型對應的一個處理器。

/**   * @author Howinfun   * @desc 自定義註解,標識支付類型   * @date 2019/11/2   */  @Target(ElementType.TYPE)  @Retention(RetentionPolicy.RUNTIME)  @Documented  @Inherited  public @interface PayWayHandler {        String value();  }

2、接著,抽象一個處理器,讓每個支付方式的處理器繼承此抽象處理器,實現handle方法:

/**   * @author Howinfun   * @desc 抽象訂單類型處理器   * @date 2019/11/2   */  public abstract class AbstractPayWayHandler {        /**       *       * @param query       * @return       */      abstract public Result handler(ChargeQuery query);  }

3、實現類,例如支付寶、微信、銀聯:
注意:每個處理器都要加上@component,交給Spring管理。

/**   * @author Howinfun   * @desc 支付寶支付   * @date 2019/11/2   */  @Component  @PayWayHandler("ALI")  @Slf4j  @AllArgsConstructor  public class AliPayWayHandler extends AbstractPayWayHandler {        // ....各種依賴        @Override      public Result handler(ChargeQuery query) {          Result result = new Result();          // ......          return result;      }  }    /**   * @author Howinfun   * @desc 微信支付   * @date 2019/11/2   */  @Component  @PayWayHandler("WX")  @Slf4j  @AllArgsConstructor  public class WxPayWayHandler extends AbstractPayWayHandler {        // ....各種依賴        @Override      public Result handler(ChargeQuery query) {          Result result = new Result();          // ......          return result;      }  }    /**   * @author Howinfun   * @desc 銀聯支付   * @date 2019/11/2   */  @Component  @PayWayHandler("UNION")  @Slf4j  @AllArgsConstructor  public class UnionPayWayHandler extends AbstractPayWayHandler {        // ....各種依賴        @Override      public Result handler(ChargeQuery query) {          Result result = new Result();          // ......          return result;      }  }

4、然後最重點的來了,創建一個類,實現ApplicationContextAware介面,重寫setApplicationContext方法,然後掃描帶有自定義註解@PayWayHandler的Bean,然後存儲起來,方便Service的獲取。

/**   * @author Howinfun   * @desc   * @date 2019/11/2   */  @Component  public class PayWayHandlerContext implements ApplicationContextAware {        @Autowired ApplicationContext applicationContext;        /** key為PayWay,value為class*/      private static final Map<String,Class> handlerMap = new HashMap<>(10);        public AbstractPayWayHandler getHandlerInstance(String payType){          Class clazz = handlerMap.get(payType);          if (clazz == null){              throw new CustomDeniedException("暫不支援此支付方式");          }          return (AbstractPayWayHandler) applicationContext.getBean(clazz);      }        @Override      public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {          // 遍歷帶有PayTypeHandler注釋的類          Map<String,Object> beans = applicationContext.getBeansWithAnnotation(PayWayHandler.class);          if (beans != null && beans.size() > 0) {              for (Object serviceBean : beans.values()) {                  String payType = serviceBean.getClass().getAnnotation(PayWayHandler.class).value();                  handlerMap.put(payType, serviceBean.getClass());              }          }      }  }

總結:到此,ServiceImpl可根據前端傳過來的payWay來選擇對應的handler來處理。我利用了策略模式簡化了繁雜的 if else 程式碼,並且擴展性得到了大大的提升,不再擔心因為支付方式的新增而修改業務程式碼。