

作者:小傅哥 博客:https://bugstack.cn
❝沉淀、分享、成长,让自己和他人都能有所收获!😜 ❞
大家好,我是技术UP主小傅哥。
凡是不具备面向对象思维,不善用设计模式解决复杂场景逻辑的,其实都只是把产品流程图,复制粘贴到 IntelliJ IDEA 代码工程里而已。这群死鬼,能把一个类里的编码行数撑到爆,少则几千,重则上万!

不是设计模式没有用,是不会设计模式的人没有用
其实对于设计模式的使用,简单来讲,就是你是否具备把控代码的能力。尤其是遇到复杂的业务逻辑时候,怎么把这些逻辑分区分块的设计实现,而不是都叠加到一个类,或者一个方法里。
所以,在你看到有设计模式编码的工程,都会清晰的看到工程的包下,设计出的分区,每个分区包下,又划分了业务、规则、服务等,用类划分了业务,所以代码的可维护性会更高。
接下来,小傅哥给大家分享一个设计模式框架(星球「码农会锁」扳手工程项目),这个框架是小傅哥基于做的非常多的业务项目,提炼出来的通用设计模式组件,可以让非常多的业务场景使用。对小白使用设计模式来说,非常友好,不用重复写设计模式代码啦!
文末还提供了,使用设计模式解决实际场景问题的业务项目,学习后非常提高编程思维和编码能力!
做业务功能开发的程序员,所承接的需求,从产品的PRD文档看,基本就是一个流程走完,走下一个流程。这个过程,包括调用了外部的接口,可能是锁券,也可能是风控,还可能是账户,或则会是协议,以及配置中心获取数据。再通过一些加工判断进行落库和发送MQ,并返回最终结果。
可能这样咋一看也不复杂,一个类里写多个方法也可以搞得定,但产品的流程是不断迭代的,当有几十个甚至到上百个流程加入进来后,它的需求会把你压的喘不过气。牵一发动全身,感觉产品就写了一点文档,但你要改的代码,甚至包括了全部的流程。因为产品的文档,是每次都新提的,但你的代码可不是每次都从0到1新写的。
就像这么这段下单的代码;

你要经过一些列的接口调用,封装最终的执行逻辑。包括;基本校验、身份校验、设备校验、人脸识别、支付环境、账户信息、风控信息、订单库存、优惠判断(组合优惠)、购物车数据、父子单-拆单等等。一些列操作。
这些流程代码,在做任何区分和设计时,都可能直接放到一个类的一个方法或者多个方法里去做处理。那么下次产品要迭代新的功能时,其实你的工作量是成倍增加的。
所以,我们也一直和这样的代码做”斗争”,想办法让代码像乐高积木一样,先按照不同的场景拆分出不同的服务类,同一属性多种形态则使用策略模式。之后在通过规则树和责任链的叠加,组合,替换,按照不同的业务,配置出不同的流程结构。
其实,通常解决复杂业务流程处理的设计模式,主要包括;责任链、规则树(多节点流程转换)、策略模式(Map key->value),这些设计模式可以解决80%的复杂业务流程编码问题。
我们有这样一种场景,在一个大的业务流程中,A产品功能,需要过01、02、04规则。B产品功能,需要过01、03、04规则。同时B产品功能,会在03规则命中时候,需要返回到前端,让用户完成人脸识别,之后在继续走B产品功能,这个时候03规则要放行,之后进入04规则。
那么,为了让功能实现的代码更加优雅,01、02、03、04,甚至更多的规则,就要分类的实现出对应的 filter 过滤节点。这些节点需要使用责任链实现,让他们可以被自由装配,并由工厂🏭配置 @Bean 对象,之后由枚举策略类提供服务(不同的枚举值对应产品,之后配置对应的执行策略 Bean 的名称)。

这个提炼出来的设计模式,看着不复杂,但很精妙,可以适配非常多的业务场景进行使用。

public class BusinessLinkedList<T, D extends DynamicContext, R> extends LinkedList<ILogicHandler<T, D, R>> implements ILogicHandler<T, D, R> {
public BusinessLinkedList(String name) {
super(name);
}
@Override
public R apply(T requestParameter, D dynamicContext) throws Exception {
Node<ILogicHandler<T, D, R>> current = this.first;
do {
ILogicHandler<T, D, R> item = current.item;
try {
// 1. 前置调用
R applyBefore = item.applyBefore(requestParameter, dynamicContext);
if (!dynamicContext.isProceed()) {
item.applyAfter(requestParameter, dynamicContext, applyBefore);
return applyBefore;
}
// 2. 节点跳过
if (dynamicContext.isJump()) {
current = current.next;
continue;
}
// 3. 执行节点
R apply = item.apply(requestParameter, dynamicContext);
if (!dynamicContext.isProceed()) {
item.applyAfter(requestParameter, dynamicContext, apply);
return apply;
}
current = current.next;
} catch (Exception e) {
item.applyAfterException(requestParameter, dynamicContext, e);
throw e;
}
} while (null != current);
thrownew RuntimeException("current item dynamic proceed is error");
}
}
接下来,我们在看下如何使用的这个责任链。
@Slf4j
@Service
public class RuleLogic201 implements ILogicHandler<String, Rule02TradeRuleFactory.DynamicContext, XxxResponse> {
public XxxResponse apply(String requestParameter, Rule02TradeRuleFactory.DynamicContext dynamicContext) throws Exception{
log.info("link model02 RuleLogic201");
return next(requestParameter, dynamicContext);
}
}
@Slf4j
@Service
publicclass RuleLogic202 implements ILogicHandler<String, Rule02TradeRuleFactory.DynamicContext, XxxResponse> {
@Override
public XxxResponse applyBefore(String requestParameter, Rule02TradeRuleFactory.DynamicContext dynamicContext) throws Exception {
if ("1".equals(requestParameter)) {
return jump(requestParameter, dynamicContext, new XxxResponse("00000"));
} elseif ("2".equals(requestParameter)){
return stop(requestParameter, dynamicContext, new XxxResponse("applyBefore 拦截结果"));
}
return next(requestParameter, dynamicContext);
}
public XxxResponse apply(String requestParameter, Rule02TradeRuleFactory.DynamicContext dynamicContext) throws Exception {
log.info("link model02 RuleLogic202");
return next(requestParameter, dynamicContext);
}
@Override
public void applyAfter(String requestParameter, Rule02TradeRuleFactory.DynamicContext dynamicContext, XxxResponse result) throws Exception {
log.info("正常结果拦截 {}", JSON.toJSONString(result));
}
@Override
public void applyAfterException(String requestParameter, Rule02TradeRuleFactory.DynamicContext dynamicContext, Exception e) throws Exception {
log.info("异常结果拦截 {}", e.getMessage());
}
}
@Slf4j
@Service
public class RuleLogic203 implements ILogicHandler<String, Rule02TradeRuleFactory.DynamicContext, XxxResponse> {
@Override
public XxxResponse apply(String requestParameter, Rule02TradeRuleFactory.DynamicContext dynamicContext) throws Exception {
log.info("link model02 RuleLogic203");
// Integer.parseInt("xxx");
return stop(requestParameter, dynamicContext, new XxxResponse("hi 小傅哥!"));
}
}
Integer.parseInt("xxx") 测试代码,让它可以抛异常。@Service
publicclass Rule02TradeRuleFactory {
@Bean("demo01")
public BusinessLinkedList<String, DynamicContext, XxxResponse> demo01(RuleLogic201 ruleLogic201,
RuleLogic202 ruleLogic202,
RuleLogic203 ruleLogic203) {
LinkArmory<String, DynamicContext, XxxResponse> linkArmory = new LinkArmory<>("demo01", ruleLogic201, ruleLogic202, ruleLogic203);
return linkArmory.getLogicLink();
}
@Bean("demo02")
public BusinessLinkedList<String, DynamicContext, XxxResponse> demo02(RuleLogic202 ruleLogic202, RuleLogic203 ruleLogic203) {
LinkArmory<String, DynamicContext, XxxResponse> linkArmory = new LinkArmory<>("demo02", ruleLogic202, ruleLogic203);
return linkArmory.getLogicLink();
}
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
publicstaticclass DynamicContext extends cn.bugstack.wrench.design.framework.link.model2.DynamicContext {
private String age;
}
}
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class Link02Test {
@Resource(name = "demo01")
private BusinessLinkedList<String, Rule02TradeRuleFactory.DynamicContext, XxxResponse> businessLinkedList01;
@Resource(name = "demo02")
private BusinessLinkedList<String, Rule02TradeRuleFactory.DynamicContext, XxxResponse> businessLinkedList02;
@Test
public void test_model02_01() throws Exception {
// requestParameter 1\2\3 测试3个流程
XxxResponse apply = businessLinkedList01.apply("3", new Rule02TradeRuleFactory.DynamicContext());
log.info("测试结果:{}", JSON.toJSONString(apply));
}
@Test
public void test_model02_02() throws Exception {
XxxResponse apply = businessLinkedList01.apply("123", new Rule02TradeRuleFactory.DynamicContext());
log.info("测试结果:{}", JSON.toJSONString(apply));
}
}
入参为1时
25-12-07.15:20:05.452 [main ] INFO RuleLogic201 - link model02 RuleLogic201
25-12-07.15:20:10.760 [main ] INFO RuleLogic203 - link model02 RuleLogic203
25-12-07.15:20:12.184 [main ] INFO RuleLogic203 - 正常结果拦截 {"age":"hi 小傅哥!"}
25-12-07.15:20:12.185 [main ] INFO Link02Test - 测试结果:{"age":"hi 小傅哥!"}
入参为3时
25-12-07.15:12:36.600 [main ] INFO RuleLogic201 - link model02 RuleLogic201
25-12-07.15:12:36.600 [main ] INFO RuleLogic202 - link model02 RuleLogic202
25-12-07.15:12:36.600 [main ] INFO RuleLogic203 - link model02 RuleLogic203
25-12-07.15:12:36.676 [main ] INFO RuleLogic203 - 正常结果拦截 {"age":"hi 小傅哥!"}
25-12-07.15:12:36.676 [main ] INFO Link02Test - 测试结果:{"age":"hi 小傅哥!"}
我们有这样一种场景,一个大的产品功能线,是要过很多流程的,这些流程牵扯着不同的业务属性,如;授信、账户、风控、配置、营销、推荐,之后又要到,不同的产品线上,如A产品功能、B产品功能。
那么,规则树的编排就很适合这样的场景使用。它的设计可以像组装乐高积木,改一个大楼一样,有非常多的延展,来满足我们的场景诉求。


public abstractclass AbstractMultiThreadStrategyRouter<T, D extends DynamicContext, R> implements StrategyMapper<T, D, R>, StrategyHandler<T, D, R> {
@Getter
@Setter
protected StrategyHandler<T, D, R> defaultStrategyHandler = StrategyHandler.DEFAULT;
public R router(T requestParameter, D dynamicContext) throws Exception {
StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);
if (null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);
return defaultStrategyHandler.apply(requestParameter, dynamicContext);
}
@Override
public R apply(T requestParameter, D dynamicContext) throws Exception {
try {
// 前置处理
R applyBefore = applyBefore(requestParameter, dynamicContext);
if (null != applyBefore) return applyBefore;
// 异步加载数据
multiThread(requestParameter, dynamicContext);
// 业务流程受理
R apply = doApply(requestParameter, dynamicContext);
// 后置处理
applyAfter(requestParameter, dynamicContext, apply);
return apply;
} catch (Exception e) {
// 后置处理(异常)
applyAfterException(requestParameter, dynamicContext, e);
throw e;
}
}
// ... 省略部分代码
}

- 在测试模块下,设计了很多的节点,这些节点可以进行业务流转模拟。 - router 是表示 doApply 受理完业务以后,走到下一个节点。
@Slf4j
@Component
publicclass AccountNode extends AbstractXxxSupport {
@Autowired
private MemberLevel1Node memberLevel1Node;
@Autowired
private MemberLevel2Node memberLevel2Node;
@Resource
private ThreadPoolExecutor threadPoolExecutor;
@Override
protected String applyBefore(String requestParameter, DefaultStrategyFactory.DynamicContext dynamicContext) {
if ("1".equals(requestParameter)){
return"xxx";
}
returnsuper.applyBefore(requestParameter, dynamicContext);
}
/**
* 1. 可执行多线程异步操作,尤其在需要大量加载数据的时候非常有用
* 2. multiThread 在需要的节点就重写,不需要的节点不用处理
*/
@Override
protected void multiThread(String requestParameter, DefaultStrategyFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<String> accountType01 = CompletableFuture.supplyAsync(() -> {
log.info("异步查询账户标签,账户标签;开户|冻结|止付|可用");
returnnew Random().nextBoolean() ? "账户冻结" : "账户可用";
}, threadPoolExecutor);
CompletableFuture<String> accountType02 = CompletableFuture.supplyAsync(() -> {
log.info("异步查询授信数据,拦截|已授信|已降档");
returnnew Random().nextBoolean() ? "拦截" : "已授信";
}, threadPoolExecutor);
CompletableFuture.allOf(accountType01, accountType02)
.thenRun(() -> {
dynamicContext.setValue("accountType01", accountType01.join());
dynamicContext.setValue("accountType02", accountType02.join());
}).join();
}
@Override
protected String doApply(String requestParameter, DefaultStrategyFactory.DynamicContext dynamicContext) throws Exception {
log.info("【账户节点】规则决策树 userId:{}", requestParameter);
Integer.parseInt("1xxx");
// 模拟查询用户级别
int level = new Random().nextInt(2);
log.info("模拟查询用户级别 level:{}", level);
dynamicContext.setLevel(level);
return router(requestParameter, dynamicContext);
}
@Override
public StrategyHandler<String, DefaultStrategyFactory.DynamicContext, String> get(String requestParameter, DefaultStrategyFactory.DynamicContext dynamicContext) throws Exception {
String accountType01 = dynamicContext.getValue("accountType01");
String accountType02 = dynamicContext.getValue("accountType02");
int level = dynamicContext.getLevel();
if ("账户冻结".equals(accountType01)) {
return memberLevel1Node;
}
if ("拦截".equals(accountType02)) {
return memberLevel1Node;
}
if (level == 1) {
return memberLevel1Node;
}
return memberLevel2Node;
}
}
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest()
public class DesignFrameworkTest {
@Resource
private DefaultStrategyFactory defaultStrategyFactory;
@Test
public void test() throws Exception {
StrategyHandler<String, DefaultStrategyFactory.DynamicContext, String> strategyHandler = defaultStrategyFactory.strategyHandler();
String result = strategyHandler.apply("xiaofuge", new DefaultStrategyFactory.DynamicContext());
log.info("测试结果:{}", result);
}
}
25-12-07.16:15:14.588 [main ] INFO RootNode - 【开关节点】规则决策树 userId:xiaofuge
25-12-07.16:15:14.589 [main ] INFO SwitchRoot - 【开关节点】规则决策树 userId:xiaofuge
25-12-07.16:15:14.592 [pool-2-thread-1 ] INFO AccountNode - 异步查询账户标签,账户标签;开户|冻结|止付|可用
25-12-07.16:15:14.592 [pool-2-thread-2 ] INFO AccountNode - 异步查询授信数据,拦截|已授信|已降档
25-12-07.16:15:14.592 [main ] INFO AccountNode - 【账户节点】规则决策树 userId:xiaofuge
25-12-07.16:15:14.592 [main ] INFO RootNode - 处理异常,applyAfterException - 用于做日志、监控和mq处理For input string: "1xxx"
java.lang.NumberFormatException: For input string: "1xxx"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
这里小傅哥举例下,在星球「码农会锁」中,各类项目对于设计模式框架的使用。
扫码加入,获得全部项目


好啦,本次本想完结。如果你希望提高自己的编程思维和编码能力,让自己具备非常强的竞争力,那么一定要好好学习下这些思维和案例,让自己的简历更有的写,让面试更有东西讲,让在公司更有底气工作。