如何在Spring Boot專案中巧妙利用策略模式幹掉if else!
直入主題
我們都知道,設計模式(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 程式碼,並且擴充套件性得到了大大的提升,不再擔心因為支付方式的新增而修改業務程式碼