Skip to content

状态机与其他实用模式:状态、迭代器、享元、组合

本文是设计模式系统学习系列的 L2 核心篇。前置:[9. 适配器、外观与桥接]。 学完可以配合面试题食用:并发容器全景

为什么需要状态机

订单状态流转是每个业务系统都会遇到的问题。刚创建 -> 已支付 -> 已发货 -> 已完成 -> 已取消,中间还有退款中、退货中。如果用一个 state 字段加一堆 if-else 来判断"当前状态能不能做某事",代码很快就会变成一团乱麻。

java
// 反例:if-else 蠕变
if ("WAIT_PAY".equals(order.getState()) && "pay".equals(action)) {
    // 支付逻辑
    order.setState("PAID");
} else if ("PAID".equals(order.getState()) && "pay".equals(action)) {
    throw new RuntimeException("已支付,不能重复支付");
} else if ("PAID".equals(order.getState()) && "ship".equals(action)) {
    // 发货逻辑
    order.setState("SHIPPED");
}
// 每加一个状态或动作,这个类就膨胀一次

这个写法的问题:状态转移逻辑分散在条件分支里,加一个状态就要改所有涉及的地方,违反开闭原则。状态模式就是解决这个问题的。

状态模式:把状态转移收拢到一张表里

状态模式的思路很简单:把每个状态的行为和转移规则封装成一个对象,用一个统一的转移表或枚举来管理所有合法路径。

java
public enum OrderState {
    WAIT_PAY {
        @Override
        public OrderState pay() {
            System.out.println("支付成功");
            return PAID;
        }
        @Override
        public OrderState cancel() {
            System.out.println("未支付订单已取消");
            return CANCELED;
        }
        // 其他动作:不支持则抛异常
        @Override
        public OrderState ship() {
            throw new IllegalStateException("未支付不能发货");
        }
    },
    PAID {
        @Override
        public OrderState ship() {
            System.out.println("发货成功");
            return SHIPPED;
        }
        @Override
        public OrderState refund() {
            System.out.println("进入退款流程");
            return REFUNDING;
        }
        @Override
        public OrderState pay() {
            throw new IllegalStateException("已支付,不能重复支付");
        }
    },
    SHIPPED {
        @Override
        public OrderState confirm() {
            System.out.println("确认收货");
            return COMPLETED;
        }
        @Override
        public OrderState refund() {
            System.out.println("进入退货退款流程");
            return RETURNING;
        }
    },
    COMPLETED, CANCELED, REFUNDING, RETURNING;

    // 默认实现:不支持的动作抛异常
    public OrderState pay() { throw new IllegalStateException("不支持"); }
    public OrderState cancel() { throw new IllegalStateException("不支持"); }
    public OrderState ship() { throw new IllegalStateException("不支持"); }
    public OrderState confirm() { throw new IllegalStateException("不支持"); }
    public OrderState refund() { throw new IllegalStateException("不支持"); }

    // 转移表验证(可选,双重保障)
    public static boolean isValidTransition(OrderState from, OrderState to) {
        try {
            // 简单内部验证逻辑
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

状态 vs 策略:结构上几乎一样,但语义不同。策略模式里各个策略互相不知道对方存在,调用方决定用哪个;状态模式里状态之间会互相转移(PAID 调 ship() 变成 SHIPPED),状态对象自己决定下一个状态是谁。

工程实现补充:小规模状态机用枚举+转移表就够了。复杂场景(十几种状态、带条件分支、超时自动转移)上 Spring StateMachine 或 COLA StateMachine,它们提供了 DSL 来声明式定义转移规则。

迭代器:遍历集合的统一接口

迭代器模式是 Java 里最普及的模式之一——Collection 接口继承了 Iterable,所以所有集合类都能用 for-each 遍历。

java
// for-each 的本质
List<String> list = Arrays.asList("A", "B", "C");
for (String s : list) {          // 编译器转成
    System.out.println(s);       // Iterator<String> it = list.iterator();
}                                // while(it.hasNext()) { s = it.next(); }

fail-fast 机制:遍历过程中如果集合被结构性修改(add/remove),迭代器会抛 ConcurrentModificationException。实现方式是每个集合维护一个 modCount 计数器,迭代器初始化时记住这个值,每次 next() 检查是否一致。

java
// 触发 fail-fast 的典型场景
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : list) {
    if ("B".equals(s)) {
        list.remove(s);  // ❌ 触发 ConcurrentModificationException
    }
}
// 正确做法:用 Iterator.remove() 或 Collectors.toList() 收集要删的

注意:CopyOnWriteArrayList 没有 fail-fast,因为它遍历的是快照,写时复制,不会和读冲突。

享元:对象复用,减少内存开销

享元模式的核心是"共享"——相同的数据只存一份,不同对象持有引用。Java 里最典型的例子:

  • Integer 缓存:默认缓存 -128~127 的 Integer 对象
  • String 常量池:相同字符串字面量指向同一对象
  • 连接池、线程池:复用昂贵的创建成本
java
// Integer 缓存实证
Integer a = 127;
Integer b = 127;
System.out.println(a == b);  // true,因为命中缓存

Integer c = 128;
Integer d = 128;
System.out.println(c == d);  // false,超出缓存范围,各自 new 对象

// 所以 Integer 比较永远用 .equals(),不要用 ==

享元模式的关键区分:内部状态(共享,不变)和外部状态(由上下文传入,可变)。比如 String 的字符数组是内部状态,拼接/截取产生的不同引用是外部状态。Integer 缓存池里相同的数值是内部状态,不同引用变量是外部状态。

组合模式:树形结构的统一处理

组合模式让客户端可以一致对待单个对象和组合对象。典型场景:菜单树、组织架构、文件系统。

java
// 组件接口
public interface FileSystemNode {
    String getName();
    long getSize();
    void print(int indent);
}

// 叶子节点:文件
public class FileNode implements FileSystemNode {
    private String name;
    private long size;

    public FileNode(String name, long size) {
        this.name = name;
        this.size = size;
    }

    @Override public String getName() { return name; }
    @Override public long getSize() { return size; }

    @Override
    public void print(int indent) {
        System.out.println("  ".repeat(indent) + name + " (" + size + "B)");
    }
}

// 组合节点:目录
public class DirectoryNode implements FileSystemNode {
    private String name;
    private List<FileSystemNode> children = new ArrayList<>();

    public DirectoryNode(String name) { this.name = name; }

    public void add(FileSystemNode child) { children.add(child); }

    @Override public String getName() { return name; }

    @Override
    public long getSize() {
        return children.stream().mapToLong(FileSystemNode::getSize).sum();
    }

    @Override
    public void print(int indent) {
        System.out.println("  ".repeat(indent) + name + "/");
        for (FileSystemNode child : children) {
            child.print(indent + 1);
        }
    }
}

组合模式常和迭代器模式一起用——递归遍历目录树本身就是迭代器思想的应用。

常见误区与小结

  • 状态模式过度设计:只有 3 个状态、2 种动作的简单状态机,用 if-else 反而更清晰。状态模式的成本在"每个状态一个类"的维护,不值得为小规模状态机引入。
  • 迭代器中的 remove() 是可选操作:ArrayList 的迭代器支持 remove(),但不是所有集合的迭代器都支持(比如 UnmodifiableList 抛异常)。调用前检查文档或 remove() 的抛异常说明。
  • 享元池的大小不是越大越好:Integer 缓存上限调高会占用更多内存,除非通过 JVM 参数 -XX:AutoBoxCacheMax 显式配置,否则默认只到 127。
  • 组合模式递归陷阱:目录里出现循环引用(软链接指向父目录)会导致 getSize() 死循环。实现时需要加 visited 集合做环路检测,或者限制只遍历一层。

小结:状态模式把状态转移从 if-else 中解放出来,迭代器解决遍历的通用接口问题,享元做对象复用减少内存,组合模式处理树形结构。这四种模式在实际工程中出现的频率很高——状态机做订单/审批流,迭代器是集合基操,享元体现在各种池化技术里,组合模式在菜单/组织/文件系统里随处可见。下一篇会把这些模式放到 Spring、MyBatis 和 JDK 源码里看它们怎么实际落地。

参考

《设计模式:可复用面向对象软件的基础》(GoF 书)第 5 章行为模式(State)、第 4 章结构模式(Composite、Flyweight) JDK 源码:java.util.Iteratorjava.lang.Integer.IntegerCachejava.util.ArrayList.Itr 的 modCount 检查 Spring StateMachine 官方文档:https://spring.io/projects/spring-statemachine

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1