首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >把设计模式开发成组件,复用性嘎嘎好!

把设计模式开发成组件,复用性嘎嘎好!

作者头像
小傅哥
发布2025-12-21 13:52:07
发布2025-12-21 13:52:07
870
举报

持续坚持原创输出,点击蓝字关注我吧

作者:小傅哥 博客:https://bugstack.cn

❝沉淀、分享、成长,让自己和他人都能有所收获!😜 ❞

大家好,我是技术UP主小傅哥。

凡是不具备面向对象思维,不善用设计模式解决复杂场景逻辑的,其实都只是把产品流程图,复制粘贴到 IntelliJ IDEA 代码工程里而已。这群死鬼,能把一个类里的编码行数撑到爆,少则几千,重则上万!

不是设计模式没有用,是不会设计模式的人没有用

其实对于设计模式的使用,简单来讲,就是你是否具备把控代码的能力。尤其是遇到复杂的业务逻辑时候,怎么把这些逻辑分区分块的设计实现,而不是都叠加到一个类,或者一个方法里。

所以,在你看到有设计模式编码的工程,都会清晰的看到工程的包下,设计出的分区,每个分区包下,又划分了业务规则服务等,用类划分了业务,所以代码的可维护性会更高。

接下来,小傅哥给大家分享一个设计模式框架(星球「码农会锁」扳手工程项目),这个框架是小傅哥基于做的非常多的业务项目,提炼出来的通用设计模式组件,可以让非常多的业务场景使用。对小白使用设计模式来说,非常友好,不用重复写设计模式代码啦!

文末还提供了,使用设计模式解决实际场景问题的业务项目,学习后非常提高编程思维和编码能力!

一、适用场景

做业务功能开发的程序员,所承接的需求,从产品的PRD文档看,基本就是一个流程走完,走下一个流程。这个过程,包括调用了外部的接口,可能是锁券,也可能是风控,还可能是账户,或则会是协议,以及配置中心获取数据。再通过一些加工判断进行落库和发送MQ,并返回最终结果。

可能这样咋一看也不复杂,一个类里写多个方法也可以搞得定,但产品的流程是不断迭代的,当有几十个甚至到上百个流程加入进来后,它的需求会把你压的喘不过气。牵一发动全身,感觉产品就写了一点文档,但你要改的代码,甚至包括了全部的流程。因为产品的文档,是每次都新提的,但你的代码可不是每次都从0到1新写的。

就像这么这段下单的代码

你要经过一些列的接口调用,封装最终的执行逻辑。包括;基本校验、身份校验、设备校验、人脸识别、支付环境、账户信息、风控信息、订单库存、优惠判断(组合优惠)、购物车数据、父子单-拆单等等。一些列操作。

这些流程代码,在做任何区分和设计时,都可能直接放到一个类的一个方法或者多个方法里去做处理。那么下次产品要迭代新的功能时,其实你的工作量是成倍增加的。

所以,我们也一直和这样的代码做”斗争”,想办法让代码像乐高积木一样,先按照不同的场景拆分出不同的服务类,同一属性多种形态则使用策略模式。之后在通过规则树和责任链的叠加,组合,替换,按照不同的业务,配置出不同的流程结构。

其实,通常解决复杂业务流程处理的设计模式,主要包括;责任链、规则树(多节点流程转换)、策略模式(Map key->value),这些设计模式可以解决80%的复杂业务流程编码问题。

二、设计模式

1. 责任链

我们有这样一种场景,在一个大的业务流程中,A产品功能,需要过01、02、04规则。B产品功能,需要过01、03、04规则。同时B产品功能,会在03规则命中时候,需要返回到前端,让用户完成人脸识别,之后在继续走B产品功能,这个时候03规则要放行,之后进入04规则

那么,为了让功能实现的代码更加优雅,01、02、03、04,甚至更多的规则,就要分类的实现出对应的 filter 过滤节点。这些节点需要使用责任链实现,让他们可以被自由装配,并由工厂🏭配置 @Bean 对象,之后由枚举策略类提供服务(不同的枚举值对应产品,之后配置对应的执行策略 Bean 的名称)。

2.1 模型设计
  • 左侧,是实现了 ILogicHandler 的责任链节点,并有 BusinessLinkedList 进行组装。这些节点可以被自由组装使用,满足不同场景的业务流程。
  • 右侧,是流程节点执行示意图。每一个节点,都有4个方法;
    • apply 是手里业务流程的,执行当前责任链具体的业务。
    • appliyBefore 是受理前,做的一些列判断,以及是否跳过当前责任链节点,还是直接返回结果,还是继续当前节点。这样可以满足不同场景的使用诉求。
    • applyAfter 是受理后,拿到执行结果,可以做的一些扩展流程,如日志、监控、MQ等。
    • applyAfterException 同上,受理后,如果是发生异常了,那么要做的流程处理。

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

2.2 模型框架
2.2.1 工程结构
  • chain - 定义链路装配和执行,LinkeList 是重写了 Java 的代码,重新写一份方便自己做扩展。
  • handler - 是每一个具体执行的方法,包括;受理的 apply、applyBefore、applyAfter、applyAfterException,以及流程节点转换的 next、stop、jump
2.2.2 核心代码
代码语言:javascript
复制
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");
    }

}
  • 关于节点流程的流转,主要由 BusinessLinkedList 类进行处理。这个过程就是拿到 LinkedList 以后,进行解链,逐个节点进行执行。
  • 执行过程为,先判断前置调用,是否要跳过,之后执行受理业务的节点 apply,完成后则湖区下一个节点。
  • applyAfter、applyAfterException,则在过程中进行记录,所有子类需要则重写此方法即可接收结果。
2.3 应用代码

接下来,我们在看下如何使用的这个责任链。

2.3.1 节点设计
代码语言:javascript
复制
@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);
    }

}
代码语言:javascript
复制
@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());
    }


}
  • RuleLogic202 过滤节点,applyBefore 根据入参进行测试,是跳过,还是结束,或者是接续本节点的 apply 方法。
  • applyAfter、applyAfterException 则负责接收最后结果。
代码语言:javascript
复制
@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 小傅哥!"));
    }

}
  • apply 受理中,增加了 Integer.parseInt("xxx") 测试代码,让它可以抛异常。
2.3.2 节点编排
代码语言:javascript
复制
@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;
    }

}
  • 所有的节点,都可以由工厂进行编排。参考 demo01、demo02 两种方式。
2.4 测试结果
代码语言:javascript
复制
@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时

代码语言:javascript
复制
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时

代码语言:javascript
复制
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 小傅哥!"}
  • 测试了 test_model02_01 入参为1和3的时候,获得到的结果。
  • 为1的时候,RuleLogic202 节点的,是跳过的,不会被执行。
  • 为3的时候,RuleLogic201、RuleLogic202、RuleLogic203 每个节点都会执行。

2. 规则树

我们有这样一种场景,一个大的产品功能线,是要过很多流程的,这些流程牵扯着不同的业务属性,如;授信、账户、风控、配置、营销、推荐,之后又要到,不同的产品线上,如A产品功能、B产品功能。

那么,规则树的编排就很适合这样的场景使用。它的设计可以像组装乐高积木,改一个大楼一样,有非常多的延展,来满足我们的场景诉求。

2.1 模型设计
  • 左侧,是复杂的业务流程节点编排,可以通过【路由】,拿到 get 方法中的节点判断和处理,走到下一个节点。与责任链相比,单链路执行适合固定的流程,如一些规则过滤。而规则树则适合编排复杂业务流程。
  • 右侧,是流程过滤提供的方法,包括了;多线程异步数据加载、业务受理、get负责路由执行时做的一些判断。而受理前、受理后、受理后异常,则在流程中进行穿插处理。
2.2 模块框架
2.2.1 工程结构
  • AbstractMultiThreadStrategyRouter 抽象类,负责 router 路由到受理业务的操作。路由会负责获取 get 方法,get 就是由每个节点自己实现的路由,告诉从此节点下面应该进入哪个节点(可能是条件判断下的多个节点)
  • apply 受理操作,则分为,前置、异步数据、业务受理、后置,之后是异常的时候处理。
2.2.2 核心代码
代码语言:javascript
复制
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 路由,是每个节点完成后,可以调用的方法,之后进入下一个节点处理,下一个节点拿到 get 路由策略,之后进行执行。
  • apply 是具体的业务受理过程,以此按照各个方法进行执行,如果 applyBefore 用户设置了 return 返回结果,则直接出去。
2.3 应用代码
2.3.1 节点设计

- 在测试模块下,设计了很多的节点,这些节点可以进行业务流转模拟。 - router 是表示 doApply 受理完业务以后,走到下一个节点。

2.3.2 实例代码
代码语言:javascript
复制
@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;
    }

}
  • 这个是从 SwitchNode 到下一个 AccountNode 节点,这个节点模拟的流程就基本和实际的业务类似了。
  • applyBefore 是做一些前置的处理,multiThread 是本节点异步数据的加载。
  • doApply 是受理业务,之后 get 是做路由操作。
  • 另外,还可以添加 applyAfter、applyAfterException 做结果的受理处理。
2.3 测试结果
代码语言:javascript
复制
@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);
    }

}
代码语言:javascript
复制
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)
  • 这是 RootNode 根节点,监控到有转换数字异常的日志打印。

三、项目举例

这里小傅哥举例下,在星球「码农会锁」中,各类项目对于设计模式框架的使用。

扫码加入,获得全部项目

1. 业务项目(拼团)

2. 技术项目(Ai Agent)

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 bugstack虫洞栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 持续坚持原创输出,点击蓝字关注我吧
  • 一、适用场景
  • 二、设计模式
    • 1. 责任链
      • 2.1 模型设计
      • 2.2 模型框架
      • 2.3 应用代码
      • 2.4 测试结果
    • 2. 规则树
      • 2.1 模型设计
      • 2.2 模块框架
      • 2.3 应用代码
      • 2.3 测试结果
  • 三、项目举例
    • 1. 业务项目(拼团)
    • 2. 技术项目(Ai Agent)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档