likes
comments
collection
share

记录一次策略模式和组合模式的实践运用

作者站长头像
站长
· 阅读数 28

记录一次策略模式和组合模式的实践运用

背景

最近公司有一个支付路由的功能,大概的需求就是创建一个支付规则,支付规则里可以根据不同的情况,对我们支付通道进行一个切换,比如说我们看设置交易笔数,根据不同的交易笔数去切换不同的支付通道,整体看不是很难,目前主要支持三种情况的设置,但是为了后续的扩展性,所以选择了策略模式+组合模式的一个方式来设计

模式介绍

相信大家都是一个成熟的程序员了,对设计模式其实都比较熟悉,尤其是当我们拥有了spring这一个bean管理的框架,其实很多设计模式都可以很方便实现。

策略模式

就是讲每个行为都封装起来,根据不同的行为去动态调用,例如我们有三个规则交易笔数,交易时间,收银员,那我们就可以定义三个行为去分别对应三个规则,然后根据不同的规则去获取对应的规则类,执行它的行为

组合模式

组合模式就将对象组合成一个集合或者树,调用可以通过一致的方法去遍历调用。例如交易时间规则下,可能还包括一些子规则比如按周循环,按月循环,交易笔数等等。我们就可以定义一系列的对象集合,通过相同的方式去执行我们的子规则

实战

首先我们的通道是分为交易笔数,交易时间,收银员等,所以我们选择策略模式,定义每种规则对应的行为,这样我们根据不同的规则类型去执行不同的行为 记录一次策略模式和组合模式的实践运用

先定义一个抽象类,指定每个规则行为,可以将公共的方法,放到抽取到抽象类中

@Component
@Slf4j
public abstract class RouterStrategy{

    @Resource
    OilUsersControlDAO oilUsersControlDAO;


    @Nullable
    public abstract RouterResult execute(RouterStrategyParam routerStrategyParam);

    /**
     * 获取规则路由状态, 1开始 2关闭
     * @param routerStrategyParam 详情
     * @return
     */
    public Integer routerStatus(RouterStrategyParam routerStrategyParam){
        OilUsersControlDO userControlByUserId = oilUsersControlDAO.getUserControlByUserId(routerStrategyParam.getBelongId());
        if (Objects.isNull(userControlByUserId)) {
            return RouterStatusEnum.CLOSED.getCode();
        }
        if (Objects.isNull(userControlByUserId.getRouterSwitch())) {
            return RouterStatusEnum.CLOSED.getCode();
        }
        return userControlByUserId.getRouterSwitch();
    }
}

继承抽象类,实现自己的行为

记录一次策略模式和组合模式的实践运用

以交易时间规则为例,定义bean名称,这样可以将所有的规则策略都交由枚举类进行管理

/**
 * 
 * @version RouterStrategyTradeTime.java, v 0.1 2024-03-12 11:01
 */
@Component("tradeTime")
@Slf4j
@AllArgsConstructor
public class RouterStrategyTradeTime extends RouterStrategy {
    private OilRuleConfigTradeTimeDAO oilRuleConfigTradeTimeDAO;

    private TradeAction tradeAction;

    @Override
    public RouterResult execute(RouterStrategyParam routerStrategyParam) {
        if (Objects.equals(routerStatus(routerStrategyParam), RouterStatusEnum.CLOSED.getCode())) {
            return null;
        }
        RouterResult routerResult = null;
        LogUtil.info(log, "RouterStrategyTradeTime.execute 开始执行规则 >> routerStrategyParam{}", routerStrategyParam);
        List<OilRuleConfigTradeTimeDO> ruleList = oilRuleConfigTradeTimeDAO.findRuleList(routerStrategyParam.getRuleId());
        LogUtil.info(log, "RouterStrategyTradeTime.execute 获取规则详情 >> ruleList{}", ruleList);
        for (OilRuleConfigTradeTimeDO oilRuleConfigTradeTimeDO : ruleList) {
            // 交易金额不为空则判断
            if (tradeAction.compareTradeAmount(oilRuleConfigTradeTimeDO.getMinPaymentAmount(), BigDecimal.ZERO) != BigDecimalResultConstants.COMPARE_RESULT) {
                if (tradeAction.compareTradeAmount(routerStrategyParam.getPayAmount(), oilRuleConfigTradeTimeDO.getMinPaymentAmount()) < BigDecimalResultConstants.COMPARE_RESULT) {
                    continue;
                }
                if (tradeAction.compareTradeAmount(routerStrategyParam.getPayAmount(), oilRuleConfigTradeTimeDO.getMaxPaymentAmount()) > BigDecimalResultConstants.COMPARE_RESULT) {
                    continue;
                }
            }
            // 交易笔数不为空则判断
            if (oilRuleConfigTradeTimeDO.getTradeCount() != BigDecimalResultConstants.COMPARE_RESULT) {
                // 当前交易笔数大于规则交易笔数,则跳过
                if (tradeAction.getTradeCount(routerStrategyParam.getBelongId(), routerStrategyParam.getStationId(),
                        oilRuleConfigTradeTimeDO.getMerchantId()) >= oilRuleConfigTradeTimeDO.getTradeCount()) {
                    continue;
                }
            }
            Integer timePeriodType = oilRuleConfigTradeTimeDO.getTimePeriodType();
            if (Objects.equals(TimePeriodTypeEnum.WEEK.getCode(), timePeriodType)) {
                // 判断周
                routerResult = compareWeek(oilRuleConfigTradeTimeDO, routerStrategyParam);
            }
            if (Objects.equals(TimePeriodTypeEnum.MONTH.getCode(), timePeriodType)) {
                // 判断月
                routerResult=  compareMonth(oilRuleConfigTradeTimeDO, routerStrategyParam);
            }
            if (Objects.equals(TimePeriodTypeEnum.DATE_PERIOD.getCode(), timePeriodType)) {
                // 判断日期段
                routerResult= compareTimePeriod(oilRuleConfigTradeTimeDO, routerStrategyParam);
            }
            if (Objects.nonNull(routerResult)) {
                return routerResult;
            }
        }
        return null;
    }
	....
}

定义策略类枚举

/**
 * 1.交易笔数、2.交易时间、3.收银员工
 *
 * @version RuleConfigType.java, v 0.1 2024-03-11 18:02 
 */
public enum RouterConfigTypeEnum {
    TRADE_COUNT(1, "交易笔数","tradeCount"),
    TRADE_TIME(2, "交易时间","tradeTime"),
    CASHIER(3, "收银员","cashier");

    private Integer configType;
    private String name;

    private String beanName;

    RouterConfigTypeEnum(Integer configType, String name, String beanName) {
        this.name = name;
        this.configType = configType;
        this.beanName = beanName;
    }

    public String getName() {
        return name;
    }

    public Integer getConfigType() {
        return configType;
    }

    public String getBeanName() {
        return beanName;
    }
    /**
     * 根据configType返回name
     */
    public static String getBeanNameByConfigType(Integer configType) {
        for (RouterConfigTypeEnum routerConfigTypeEnum : RouterConfigTypeEnum.values()) {
            if (routerConfigTypeEnum.getConfigType().equals(configType)) {
                return routerConfigTypeEnum.getBeanName();
            }
        }
        return "";
    }
}

执行路由规则

 try {
            LogUtil.info(log,"PayRouterServiceImpl.getRouter 开始获取支付规则路由 >> ruleByCurrentTime:{}",ruleByCurrentTime);
			//根据不同的规则类型获取对应的策略类          
			String beanNameByConfigType = RouterConfigTypeEnum.getBeanNameByConfigType(ruleByCurrentTime.getConfigType());
            // 实例化对应的策略类
			RouterStrategy routerStrategy = SpringUtil.getBean(beanNameByConfigType, RouterStrategy.class);
            routerStrategyParam.setRuleId(ruleByCurrentTime.getId());
            // 执行策略类
			RouterResult router = routerStrategy.execute(routerStrategyParam);
            // 规则策略执行器返回为空,则走规则默认支付路由
            if (Objects.isNull(router)) {
                routerResult = payRouterServiceMapper.toRouterResultByOilPayRuleDO(ruleByCurrentTime);
                routerResult.setMerchantId(ruleByCurrentTime.getDefaultMerchantId());
                return routerResult;
            }
            return router;
        }catch (Exception e){
            LogUtil.error(log,"PayRouterServiceImpl.getRouter 获取路由规则失败>> 入参:{},e:",routerStrategyParam,e);
        }

到这里你会发现好像少了什么,组合模式怎么不见了,所以需要再改造一下,为什么需要组合模式,因为我们每个规则下还有不同的子规则,例如交易时间多种子规则 记录一次策略模式和组合模式的实践运用

定义子规则行为

package com.oil.service.business.router.routerStrategy.combination;

import com.oil.service.domain.param.pay.RouterStrategyParam;

/**
 *
 *
 * @version SubruleAction.java, v 0.1 2024-03-30 12:02
 */
public interface SubRuleAction {

    boolean execute(RouterStrategyParam routerStrategyParam);
}

实现子规则行为 记录一次策略模式和组合模式的实践运用 通过枚举管理每个子规则组合

/**
 * @version RouterSubRuleEnum.java, v 0.1 2024-03-30 12:17
 */
package com.oil.service.enums;

import com.oil.service.business.router.routerStrategy.combination.SubRuleAction;
import com.oil.service.business.router.routerStrategy.combination.TradeAmountAction;
import com.oil.service.business.router.routerStrategy.combination.TradeCountAction;

import java.util.ArrayList;
import java.util.List;

/**
 *
 *
 * @version RouterSubRuleEnum.java, v 0.1 2024-03-30 12:17 
 */
public enum RouterSubRuleEnum {
 TRADE_COUNT(1, "交易笔数"){
        public List<SubRuleAction> getSubRule() {
            List<SubRuleAction> subRuleActions = new ArrayList<>();
            subRuleActions.add(SpringUtil.getBean("TradeCountAction",SubRuleAction.class));
            return subRuleActions;
        }
    },
    TRADE_TIME(2, "交易时间"){
        public List<SubRuleAction> getSubRule() {
            List<SubRuleAction> subRuleActions = new ArrayList<>();
            subRuleActions.add(SpringUtil.getBean("TradeCountAction",SubRuleAction.class));
            subRuleActions.add(SpringUtil.getBean("TradeAmountAction",SubRuleAction.class));
            return subRuleActions;
        }
    },
    CASHIER(3, "收银员"){
        public List<SubRuleAction> getSubRule() {
            return new ArrayList<>();
        }
    };

    private Integer configType;
    private String name;

    RouterSubRuleEnum(Integer configType, String name) {
        this.configType = configType;
        this.name = name;
    }

    public Integer getConfigType() {
        return configType;
    }

    public String getName() {
        return name;
    }
    public abstract List<SubRuleAction> getSubRule();

    public static List<SubRuleAction> getByConfigType(Integer configType) {
        for (RouterSubRuleEnum routerSubRuleEnum : RouterSubRuleEnum.values()) {
            if (routerSubRuleEnum.getConfigType().equals(configType)) {
                return routerSubRuleEnum.getSubRule();
            }
        }
        return new ArrayList<>();
    }
}

获取规则策略的同时,传入组合好的子规则

        try {
            LogUtil.info(log,"PayRouterServiceImpl.getRouter 开始获取支付规则路由 >> ruleByCurrentTime:{}",ruleByCurrentTime);
            String beanNameByConfigType = RouterConfigTypeEnum.getBeanNameByConfigType(ruleByCurrentTime.getConfigType());
            RouterStrategy routerStrategy = SpringUtil.getBean(beanNameByConfigType, RouterStrategy.class);
            routerStrategyParam.setRuleId(ruleByCurrentTime.getId());
            RouterResult router = routerStrategy.execute(routerStrategyParam
					// 获取子规则集合
                    ,RouterSubRuleEnum.getByConfigType(ruleByCurrentTime.getConfigType()));
            // 规则策略执行器返回为空,则走规则默认支付路由
            if (Objects.isNull(router)) {
                routerResult = payRouterServiceMapper.toRouterResultByOilPayRuleDO(ruleByCurrentTime);
                routerResult.setMerchantId(ruleByCurrentTime.getDefaultMerchantId());
                return routerResult;
            }
            return router;
        }catch (Exception e){
            LogUtil.error(log,"PayRouterServiceImpl.getRouter 获取路由规则失败>> 入参:{},e:",routerStrategyParam,e);
        }

交易时间规则类,执行子规则判断

  public RouterResult execute(RouterStrategyParam routerStrategyParam, List<SubRuleAction> subRuleActionList) {
        if (Objects.equals(routerStatus(routerStrategyParam), RouterStatusEnum.CLOSED.getCode())) {
            return null;
        }
        RouterResult routerResult = null;
        LogUtil.info(log, "RouterStrategyTradeTime.execute 开始执行规则 >> routerStrategyParam{}", routerStrategyParam);
        List<OilRuleConfigTradeTimeDO> ruleList = oilRuleConfigTradeTimeDAO.findRuleList(routerStrategyParam.getRuleId());
        LogUtil.info(log, "RouterStrategyTradeTime.execute 获取规则详情 >> ruleList{}", ruleList);
        for (OilRuleConfigTradeTimeDO oilRuleConfigTradeTimeDO : ruleList) {
            // 子规则判断
            AtomicBoolean result = new AtomicBoolean(true);
            subRuleActionList.forEach(subRuleAction -> 
                    result.set(subRuleAction.execute(routerStrategyParam)));
            // 校验未通过则跳过
            if (!result.get()) {
                continue;
            }
            Integer timePeriodType = oilRuleConfigTradeTimeDO.getTimePeriodType();
            if (Objects.equals(TimePeriodTypeEnum.WEEK.getCode(), timePeriodType)) {
                // 判断周
                routerResult = compareWeek(oilRuleConfigTradeTimeDO, routerStrategyParam);
            }
            if (Objects.equals(TimePeriodTypeEnum.MONTH.getCode(), timePeriodType)) {
                // 判断月
                routerResult=  compareMonth(oilRuleConfigTradeTimeDO, routerStrategyParam);
            }
            if (Objects.equals(TimePeriodTypeEnum.DATE_PERIOD.getCode(), timePeriodType)) {
                // 判断日期段
                routerResult= compareTimePeriod(oilRuleConfigTradeTimeDO, routerStrategyParam);
            }
            if (Objects.nonNull(routerResult)) {
                return routerResult;
            }
        }
        return null;
    }

总结

使用设计模式主要是为了扩展性和规范性,通过不同模式之间进行设计,可以很好的规范我们对某个功能模块的开发行为,就比如我们之后可能还需要有新的子规则,只需要再实现新的子规则类,添加到对应的组合集合中就可以完成对子规则的添加,我们既不需要理解原有代码的逻辑,也不需要改动原有代码,只需要遵从规定的规范就可以快速的实现,何乐而不为

转载自:https://juejin.cn/post/7352015721209659455
评论
请登录