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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM