状态机——并行分支聚合

并行分支聚合

1、问题描述

当你的状态机不再是简单的"单线流转"(A -> B -> C),而是涉及多条件聚合(比如 A、B、C 三个前置条件都完成后,才能触发转换为 D)时,这在状态机中被称为 Fork/Join(并行分支聚合) 场景。

在 Java 枚举中实现这种"多合一"的逻辑,最核心的变化是:状态机不能再只依赖一个"当前状态"变量,而是需要引入"状态位(Flags)"或者"事件收集池"来记录前置条件的完成情况。

2、方案一:利用 EnumSet 收集前置状态(最推荐,纯轻量级)

我们可以把 A、B、C 看作是 D 的三个前置子任务。当 A、B、C 各自完成后,检查集合是否集齐,集齐了就自动合成为 D。

2.1、定义状态和事件

java 复制代码
import java.util.EnumSet;

public enum CompositeState {
    INIT,       // 初始状态
    A_DONE,     // A部分完成
    B_DONE,     // B部分完成
    C_DONE,     // C部分完成
    D;          // 最终合成态 D
}

// 触发的事件(比如三个部门分别审批、或三个组件分别就绪)
enum Event {
    FINISH_A, FINISH_B, FINISH_C
}

2.2、状态机上下文

在 Context 中,我们用 EnumSet 来实时记录已经完成了哪些部分。

java 复制代码
public class SynthesisContext {
    private final String id;
    private CompositeState currentStatus = CompositeState.INIT;

    // 用来存放已经就绪的前置状态集合
    private final EnumSet<CompositeState> readyParts = EnumSet.noneOf(CompositeState.class);

    public SynthesisContext(String id) {
        this.id = id;
    }

    // 统一的事件触发入口
    public synchronized void handleEvent(Event event) {
        if (currentStatus == CompositeState.D) {
            System.out.println("已经处于最终态 D,忽略事件: " + event);
            return;
        }

        // 根据事件,把对应的状态加入"已就绪"池
        switch (event) {
            case FINISH_A :
                readyParts.add(CompositeState.A_DONE);
                break;
            case FINISH_B :
                readyParts.add(CompositeState.B_DONE);
                break;
            case FINISH_C :
                readyParts.add(CompositeState.C_DONE);
                break;
            default:
        }

        System.out.println("事件 " + event + " 触发,当前已就绪组件: " + readyParts);

        // 检查是否满足合成 D 的条件
        if (readyParts.contains(CompositeState.A_DONE) &&
                readyParts.contains(CompositeState.B_DONE) &&
                readyParts.contains(CompositeState.C_DONE)) {

            this.currentStatus = CompositeState.D;
            System.out.println("🎉 【🎉大功告成】A+B+C 满足条件,成功合成为状态: " + currentStatus);
        }
    }

    public CompositeState getCurrentStatus() {
        return currentStatus;
    }
}

2.3、测试运行

java 复制代码
public class Main {
    public static void main(String[] args) {
        SynthesisContext context = new SynthesisContext("Task_001");

        // 异步或乱序触发事件
        context.handleEvent(Event.FINISH_B); // 触发 B
        context.handleEvent(Event.FINISH_A); // 触发 A

        System.out.println("当前整体状态: " + context.getCurrentStatus()); // 依然不是 D
        System.out.println("-------------------------------------");

        context.handleEvent(Event.FINISH_C); // 触发 C,此时 A+B+C 齐全
        System.out.println("当前整体状态: " + context.getCurrentStatus()); // 变成 D
    }
}
复制代码
事件 FINISH_B 触发,当前已就绪组件: [B_DONE]
事件 FINISH_A 触发,当前已就绪组件: [A_DONE, B_DONE]
当前整体状态: INIT
-------------------------------------
事件 FINISH_C 触发,当前已就绪组件: [A_DONE, B_DONE, C_DONE]
🎉 【🎉大功告成】A+B+C 满足条件,成功合成为状态: D
当前整体状态: D

3、方案二:利用"二进制位(Bitmask)"进行状态压缩

如果 A、B、C 是极其严格的布尔条件(要么完,要么没完),并且你希望把这个状态极其轻量地存在数据库的一个数字字段里,可以用位运算。

  • A 完成 = 0b001 (十进制 1)
  • B 完成 = 0b010 (十进制 2)
  • C 完成 = 0b100 (十进制 4)
  • D (全部完成) = 0b111 (十进制 7)
java 复制代码
public enum BitOrderState {
    // 定义每个事件对应的二进制位
    FINISH_A(1 << 0), // 1
    FINISH_B(1 << 1), // 2
    FINISH_C(1 << 2); // 4

    private final int mask;
    BitOrderState(int mask) { this.mask = mask; }

    public int getMask() { return mask; }

    // 核心逻辑:当前状态码通过"位或"运算叠加,满 7 则代表合成 D
    public static int nextState(int currentProgress, BitOrderState event) {
        // 叠加状态
        int newProgress = currentProgress | event.getMask();
        
        if (newProgress == 7) {
            System.out.println("位状态满 0b111(7),成功合成为 D!");
        } else {
            System.out.println("当前进度二进制码: " + Integer.toBinaryString(newProgress));
        }
        return newProgress;
    }
}

使用方式:

java 复制代码
int progress = 0; // 初始状态 0
progress = BitOrderState.nextState(progress, BitOrderState.FINISH_A); // 变为 1
progress = BitOrderState.nextState(progress, BitOrderState.FINISH_C); // 变为 5 (1|4)
progress = BitOrderState.nextState(progress, BitOrderState.FINISH_B); // 变为 7 -> 触发合成 D

总结:对于 A + B + C -> D 这种多对一的合成:

  • 业务逻辑清晰、可读性高: 用 方案一(EnumSet),这也是最标准的面向对象写法。
  • 高并发、或者要存数据库高性能持久化: 用 方案二(Bitmask位运算),数据库只需要存一个 int 字段即可代表 A/B/C 的任意组合完成状态。
相关推荐
逸Y 仙X1 小时前
文章三十三:Elasticsearch 文本分词器深入实战
java·大数据·elasticsearch·搜索引擎·全文检索
optimistic_chen3 小时前
【AI Agent 全栈开发】MCP
java·linux·运维·人工智能·ai编程·mcp
2401_8332693010 小时前
Java网络编程入门
java·开发语言
金銀銅鐵10 小时前
[Java] 如何将 Lambda 表达式对应的类保存到 class 文件中?
java·后端
それども11 小时前
Gradle 构建疑难杂症 Could not find netty-transport-native-epoll-linux-aarch_64.ja
java·服务器·gradle·maven
正儿八经的少年11 小时前
application.yml 系列配置文件作用与区别
java·配置文件
鱼很腾apoc12 小时前
【学习篇】第20期 超详解 C++ 多态:从语法规则到底层原理
java·c语言·开发语言·c++·学习·算法·青少年编程
cheems952712 小时前
[Spring MVC] 统一功能与拦截器实践总结
java·spring·mvc
Full Stack Developme13 小时前
Spring Boot 事务管理完整教程
java·数据库·spring boot