1. 程式人生 > >如何在Spring Boot專案中巧妙利用策略模式幹掉if else!

如何在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 程式碼,並且擴充套件性得到了大大的提升,不再擔心因為支付方式的新增而修改業務程式碼