记录一次策略模式和组合模式的实践运用
记录一次策略模式和组合模式的实践运用
背景
最近公司有一个支付路由的功能,大概的需求就是创建一个支付规则,支付规则里可以根据不同的情况,对我们支付通道进行一个切换,比如说我们看设置交易笔数,根据不同的交易笔数去切换不同的支付通道,整体看不是很难,目前主要支持三种情况的设置,但是为了后续的扩展性,所以选择了策略模式+组合模式的一个方式来设计
模式介绍
相信大家都是一个成熟的程序员了,对设计模式其实都比较熟悉,尤其是当我们拥有了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