状态模式 Java 17 实战:3种状态转换策略与自动售货机代码重构

1. 状态模式核心思想与应用场景

当你面对一个需要根据内部状态改变行为的对象时,状态模式(State Pattern)能提供优雅的解决方案。这种设计模式将对象的行为封装在不同的状态类中,使得状态转换时行为变化就像对象改变了类一样自然。

典型应用场景

  • 自动售货机的商品售卖流程
  • 订单生命周期管理(待支付、已发货、已完成等)
  • 线程状态转换(新建、运行、阻塞等)
  • 游戏角色状态(站立、奔跑、跳跃等)

状态模式的核心优势在于:

  • 消除条件分支 :避免了大量if-else或switch-case语句
  • 开闭原则 :新增状态只需添加新类,无需修改现有代码
  • 职责清晰 :每个状态类只关注自身行为逻辑

2. 自动售货机的三种状态实现策略

2.1 Context控制策略(传统实现)

在这种实现中,状态转换逻辑由Context类(自动售货机)控制。这是最经典的状态模式实现方式。

// 状态接口
public interface VendingState {
    void insertCoin();
    void selectProduct();
    void dispense();
}

// 具体状态:未投币
public class NoCoinState implements VendingState {
    private final VendingMachine machine;
    
    public NoCoinState(VendingMachine machine) {
        this.machine = machine;
    }
    
    @Override
    public void insertCoin() {
        System.out.println("硬币已投入");
        machine.setCurrentState(machine.getHasCoinState());
    }
    
    // 其他方法实现...
}

// 自动售货机(Context)
public class VendingMachine {
    private VendingState currentState;
    private final VendingState noCoinState;
    private final VendingState hasCoinState;
    private final VendingState soldState;
    
    public VendingMachine() {
        noCoinState = new NoCoinState(this);
        hasCoinState = new HasCoinState(this);
        soldState = new SoldState(this);
        currentState = noCoinState;
    }
    
    // 状态转换和行为委托方法...
}

优缺点对比

优点 缺点
状态转换逻辑集中管理 Context类承担了较多职责
新增状态不影响现有代码 状态转换规则修改需要改动Context

2.2 State控制策略(现代实现)

在这种变体中,状态转换逻辑由各个具体状态类自行控制,Context只负责维护当前状态。

// 改进后的状态接口
public interface EnhancedVendingState {
    void insertCoin(VendingMachine machine);
    void selectProduct(VendingMachine machine);
    void dispense(VendingMachine machine);
}

// 具体状态控制转换
public class EnhancedHasCoinState implements EnhancedVendingState {
    @Override
    public void selectProduct(VendingMachine machine) {
        System.out.println("商品已选择");
        if(machine.hasStock()) {
            machine.setCurrentState(new SoldState());
        } else {
            System.out.println("商品缺货");
            machine.returnCoin();
            machine.setCurrentState(new NoCoinState());
        }
    }
    // 其他方法实现...
}

适用场景

  • 状态转换规则较为复杂
  • 不同状态需要不同的转换逻辑
  • 系统预期会有频繁的状态规则变更

2.3 枚举实现策略(简洁方案)

对于状态数量有限且行为简单的场景,可以使用枚举实现状态模式,这是Java特有的简洁实现方式。

public enum VendingStateEnum {
    NO_COIN {
        @Override
        public void insertCoin(VendingMachine machine) {
            System.out.println("接受投币");
            machine.setState(HAS_COIN);
        }
        // 其他方法默认实现...
    },
    HAS_COIN {
        @Override
        public void selectProduct(VendingMachine machine) {
            System.out.println("商品已选择");
            machine.setState(SOLD);
        }
    },
    SOLD {
        @Override
        public void dispense(VendingMachine machine) {
            machine.releaseProduct();
            machine.setState(machine.hasStock() ? NO_COIN : OUT_OF_STOCK);
        }
    };
    
    // 默认方法实现
    public void insertCoin(VendingMachine machine) {
        throw new IllegalStateException("当前状态不支持此操作");
    }
    // 其他默认方法...
}

枚举实现的优势

  • 代码极其简洁
  • 线程安全(枚举实例天然单例)
  • 状态和行为集中定义
  • 适合状态数量固定的场景

3. 代码重构实战:从传统实现到现代模式

3.1 重构前的问题分析

典型的状态模式初学者实现往往存在以下问题:

// 典型问题代码示例
public class ProblematicVendingMachine {
    private State state;
    
    public void insertCoin() {
        if(state == State.NO_COIN) {
            state = State.HAS_COIN;
        } else if(state == State.HAS_COIN) {
            // 处理重复投币...
        }
        // 更多条件判断...
    }
    // 其他方法...
}

主要问题

  1. 使用条件语句而非多态
  2. 状态转换逻辑分散
  3. 违反开闭原则
  4. 难以扩展新状态

3.2 重构步骤与技巧

步骤一:识别并提取状态接口

public interface VendingState {
    void insertCoin(VendingContext context);
    void selectProduct(VendingContext context);
    void dispense(VendingContext context);
    void cancel(VendingContext context);
}

步骤二:创建具体状态类

public class ReadyState implements VendingState {
    @Override
    public void insertCoin(VendingContext context) {
        context.addCredit();
        context.changeState(new HasCreditState());
    }
    // 其他方法实现...
}

步骤三:重构Context类

public class ModernVendingMachine implements VendingContext {
    private VendingState currentState;
    private int credit;
    private Inventory inventory;
    
    public ModernVendingMachine() {
        this.currentState = new ReadyState();
        this.inventory = new SimpleInventory();
    }
    
    @Override
    public void changeState(VendingState newState) {
        this.currentState = newState;
    }
    
    // 委托给当前状态
    public void insertCoin() {
        currentState.insertCoin(this);
    }
    // 其他委托方法...
}

重构后的优势

  • 状态转换逻辑清晰可见
  • 新增状态只需添加新类
  • 消除了条件判断语句
  • 各状态行为完全隔离

3.3 性能优化技巧

状态模式可能引入的性能问题及解决方案:

  1. 状态对象创建开销

    // 使用对象池或享元模式优化
    public class StateFactory {
        private static final Map<Class<?>, VendingState> pool = new HashMap<>();
        
        public static VendingState getState(Class<?> stateClass) {
            return pool.computeIfAbsent(stateClass, k -> {
                try {
                    return (VendingState) k.newInstance();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }
    
  2. 状态转换原子性保证

    // 使用原子引用确保线程安全
    public class ConcurrentVendingMachine {
        private final AtomicReference<VendingState> currentState;
        
        public void changeState(VendingState newState) {
            VendingState prev;
            do {
                prev = currentState.get();
            } while (!currentState.compareAndSet(prev, newState));
        }
    }
    

4. 状态模式高级应用与Java 17特性

4.1 使用Records简化状态类

Java 17的Record特性可以极大简化状态类的定义:

public record ReadyState() implements VendingState {
    @Override
    public void insertCoin(VendingContext context) {
        context.addCredit(1);
        context.changeState(new HasCreditState());
    }
    // 其他方法...
}

4.2 模式匹配简化状态处理

Java 17的模式匹配特性可以让状态处理更简洁:

public void handleState(VendingState state) {
    switch(state) {
        case ReadyState s -> processReady(s);
        case HasCreditState s -> processHasCredit(s);
        case SoldState s -> processSold(s);
        default -> throw new IllegalStateException();
    }
}

4.3 状态机与工作流集成

对于复杂业务流程,可以结合状态模式和工作流引擎:

public class OrderWorkflow {
    private OrderState currentState;
    private final WorkflowEngine engine;
    
    public void proceedToNext() {
        currentState.handle(this);
        engine.audit(this);
    }
    
    interface OrderState {
        void handle(OrderWorkflow workflow);
    }
}

状态模式与工作流引擎对比

特性 状态模式 工作流引擎
复杂度 适合中等复杂度 适合高复杂度
可维护性 代码中维护 外部配置化
持久化 需自行实现 内置支持
可视化 困难 通常支持

5. 测试策略与最佳实践

5.1 单元测试状态类

class NoCoinStateTest {
    private VendingMachine machine;
    private NoCoinState state;
    
    @BeforeEach
    void setUp() {
        machine = mock(VendingMachine.class);
        state = new NoCoinState(machine);
    }
    
    @Test
    void shouldTransitionToHasCoinOnInsert() {
        state.insertCoin();
        verify(machine).setCurrentState(any(HasCoinState.class));
    }
}

5.2 集成测试状态转换

@Test
void shouldCompletePurchaseFlow() {
    VendingMachine vm = new VendingMachine();
    vm.insertCoin();  // NO_COIN -> HAS_COIN
    vm.selectProduct(); // HAS_COIN -> SOLD
    vm.dispense();    // SOLD -> NO_COIN
    
    assertEquals(StateEnum.NO_COIN, vm.getCurrentState());
}

5.3 最佳实践清单

  1. 状态设计原则

    • 每个状态类只关注自身行为
    • 状态转换逻辑要么集中在Context,要么分散在各状态
    • 避免"上帝状态"知道太多其他状态
  2. 性能考量

    • 考虑状态对象的创建成本
    • 高频状态转换场景注意线程安全
    • 复杂状态可考虑备忘录模式保存/恢复
  3. 可维护性建议

    • 为状态转换添加日志记录
    • 使用状态模式生成器简化创建
    • 考虑可视化状态转换图

6. 状态模式与其他模式的协同

6.1 状态模式 vs 策略模式

虽然结构相似,但两者意图不同:

维度 状态模式 策略模式
目的 处理状态转换 封装算法
状态知晓 状态知道其他状态 策略相互独立
触发方式 通常由内部事件触发 通常由客户端选择

6.2 结合观察者模式

当状态变化需要通知多方时:

public class ObservableVendingMachine extends VendingMachine {
    private final List<StateChangeListener> listeners = new ArrayList<>();
    
    @Override
    public void changeState(VendingState newState) {
        var oldState = getCurrentState();
        super.changeState(newState);
        listeners.forEach(l -> l.onStateChange(oldState, newState));
    }
}

6.3 结合装饰器模式

为状态添加额外行为:

public class LoggingState implements VendingState {
    private final VendingState wrapped;
    
    @Override
    public void insertCoin(VendingContext context) {
        System.out.println("Before insertCoin");
        wrapped.insertCoin(context);
        System.out.println("After insertCoin");
    }
    // 其他装饰方法...
}

7. 自动售货机完整实现示例

以下是使用Java 17特性的完整实现:

// 状态接口
public sealed interface VendingState 
    permits NoCoinState, HasCoinState, SoldState, OutOfStockState {
    
    default void insertCoin(VendingMachine machine) {
        throw new IllegalStateException("操作不支持");
    }
    
    // 其他默认方法...
}

// 具体状态实现
public final class NoCoinState implements VendingState {
    @Override
    public void insertCoin(VendingMachine machine) {
        machine.addCredit(1);
        machine.changeState(new HasCoinState());
    }
}

// 自动售货机实现
public class VendingMachine {
    private VendingState currentState = new NoCoinState();
    private int credit;
    
    public void changeState(VendingState newState) {
        Objects.requireNonNull(newState);
        this.currentState = newState;
    }
    
    public void insertCoin() {
        currentState.insertCoin(this);
    }
    
    // 其他方法...
}

关键设计决策

  1. 使用sealed接口限制状态类型
  2. 默认方法提供安全默认行为
  3. 不可变状态对象
  4. 清晰的上下文接口

8. 常见陷阱与解决方案

8.1 状态爆炸问题

当状态过多时,解决方案:

  • 使用层次状态机(HSM)
  • 组合状态模式
  • 考虑转为状态表驱动

8.2 循环依赖问题

状态与Context相互引用时的解决方案:

// 使用接口隔离
public interface VendingContext {
    void changeState(VendingState newState);
    // 仅暴露必要方法
}

// 具体机器实现接口
public class VendingMachine implements VendingContext {
    // 实现细节...
}

8.3 状态持久化方案

将状态保存到数据库的几种方式:

  1. 枚举方式

    @Entity
    public class MachineState {
        @Enumerated(EnumType.STRING)
        private StateEnum currentState;
    }
    
  2. 类型鉴别器方式

    @Entity
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    @DiscriminatorColumn(name = "state_type")
    public abstract class PersistentState {
        // 公共字段...
    }
    

9. 性能优化实战:状态缓存与懒加载

对于创建成本高的状态对象:

public class StateHolder {
    private static final Map<Class<?>, VendingState> cache = new ConcurrentHashMap<>();
    
    public static VendingState getState(Class<?> stateClass) {
        return cache.computeIfAbsent(stateClass, clazz -> {
            try {
                return (VendingState) clazz.getDeclaredConstructor().newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }
}

结合Java 17特性:

public VendingState getState(Class<?> stateClass) {
    return cache.computeIfAbsent(stateClass, clazz -> {
        return switch(clazz.getSimpleName()) {
            case "NoCoinState" -> new NoCoinState();
            case "HasCoinState" -> new HasCoinState();
            default -> throw new IllegalArgumentException();
        };
    });
}

10. 状态模式在复杂系统中的应用

10.1 电商订单状态机

public class Order {
    private OrderState state;
    
    public void pay() {
        state.pay(this);
    }
    
    public interface OrderState {
        void pay(Order order);
        void cancel(Order order);
        // 其他订单操作...
    }
}

10.2 游戏AI状态管理

public class NPC {
    private AIState currentState;
    
    public void update() {
        currentState.execute(this);
    }
    
    public interface AIState {
        void execute(NPC npc);
        void enter(NPC npc);
        void exit(NPC npc);
    }
}

10.3 网络协议状态处理

public class ProtocolHandler {
    private ProtocolState state;
    
    public void handle(Message msg) {
        state.process(this, msg);
    }
    
    public interface ProtocolState {
        void process(ProtocolHandler handler, Message msg);
    }
}

11. 状态模式的可测试性设计

11.1 测试指导原则

  1. 每个状态类应独立测试
  2. 测试状态转换边界条件
  3. 模拟非法状态转换
  4. 验证状态行为副作用

11.2 典型测试案例

class HasCoinStateTest {
    @Test
    void shouldRefundWhenCancelling() {
        var machine = mock(VendingMachine.class);
        var state = new HasCoinState();
        
        state.cancel(machine);
        
        verify(machine).returnCoins();
        verify(machine).changeState(isA(NoCoinState.class));
    }
}

11.3 状态转换测试矩阵

起始状态 操作 预期结果状态 预期行为
NO_COIN 投币 HAS_COIN 增加余额
HAS_COIN 取消 NO_COIN 退币
HAS_COIN 选择商品(有库存) SOLD 减少库存
HAS_COIN 选择商品(无库存) NO_COIN 退币并提示

12. 状态模式与领域驱动设计

12.1 状态作为领域概念

在DDD中,状态模式可以很好地表示领域模型的状态转换:

public class BankAccount {
    private AccountState state;
    
    public void withdraw(Money amount) {
        state.withdraw(this, amount);
    }
    
    interface AccountState {
        void withdraw(BankAccount account, Money amount);
        // 其他账户操作...
    }
}

12.2 状态模式与聚合根

将状态模式应用于聚合根设计:

public class ShippingOrder implements AggregateRoot {
    private OrderState state;
    
    public void deliver() {
        state.deliver(this);
    }
    
    // 内部状态类保持聚合不变性
    private static class DeliveredState implements OrderState {
        @Override
        public void deliver(ShippingOrder order) {
            throw new IllegalStateException("订单已送达");
        }
    }
}

13. 状态模式的可视化与调试

13.1 状态转换日志

public class LoggingStateDecorator implements VendingState {
    private final VendingState delegate;
    private final Logger logger;
    
    @Override
    public void insertCoin(VendingMachine machine) {
        logger.info("Before insertCoin: {}", machine.getCurrentState());
        delegate.insertCoin(machine);
        logger.info("After insertCoin: {}", machine.getCurrentState());
    }
}

13.2 状态图生成

使用PlantUML生成状态图:

@startuml
state NoCoinState
state HasCoinState
state SoldState

[*] --> NoCoinState
NoCoinState --> HasCoinState : insertCoin
HasCoinState --> NoCoinState : cancel
HasCoinState --> SoldState : selectProduct
SoldState --> NoCoinState : dispense
@enduml

14. 状态模式的演进与替代方案

14.1 状态表驱动

对于复杂状态机,可考虑表驱动方式:

public class StateTable {
    private final Map<State, Map<Event, Transition>> table;
    
    public void handle(State current, Event event) {
        Transition transition = table.get(current).get(event);
        if(transition != null) {
            transition.execute();
        }
    }
    
    record Transition(State target, Runnable action) {
        void execute() {
            action.run();
        }
    }
}

14.2 状态模式与事件溯源

结合事件溯源实现可追溯的状态变更:

public class EventSourcedOrder {
    private OrderState state;
    private final List<Event> events = new ArrayList<>();
    
    public void apply(Event event) {
        this.state = state.transition(event);
        this.events.add(event);
    }
}

15. 状态模式在微服务架构中的应用

15.1 服务内部状态管理

@Service
public class PaymentService {
    private PaymentState state;
    
    @Transactional
    public void process(PaymentRequest request) {
        state.handle(this, request);
    }
    
    interface PaymentState {
        void handle(PaymentService service, PaymentRequest request);
    }
}

15.2 Saga模式中的状态管理

实现分布式事务的状态管理:

public class OrderSaga {
    private SagaState state;
    
    public void handle(Event event) {
        this.state = state.handle(event);
    }
    
    interface SagaState {
        SagaState handle(Event event);
    }
}

16. 状态模式与反应式编程

16.1 响应式状态机

public class ReactiveVendingMachine {
    private final AtomicReference<VendingState> state;
    private final Flux<StateEvent> stateChanges;
    
    public Mono<Void> insertCoin() {
        return Mono.fromRunnable(() -> {
            var current = state.get();
            var newState = current.onInsertCoin();
            state.compareAndSet(current, newState);
            // 发布状态变更事件...
        });
    }
}

16.2 Actor模型中的状态

public class VendingActor extends AbstractBehavior<VendingMessage> {
    private VendingState state;
    
    @Override
    public Receive<VendingMessage> createReceive() {
        return newReceiveBuilder()
            .onMessage(InsertCoin.class, this::onInsertCoin)
            .build();
    }
    
    private Behavior<VendingMessage> onInsertCoin(InsertCoin msg) {
        this.state = state.onInsertCoin();
        return this;
    }
}

17. 状态模式与函数式编程

17.1 不可变状态转换

public class FunctionalVending {
    private final VendingState state;
    
    public FunctionalVending nextState(Function<VendingState, VendingState> transition) {
        return new FunctionalVending(transition.apply(this.state));
    }
}

17.2 状态模式与Either

使用Either处理非法状态转换:

public Either<Error, VendingState> insertCoin(VendingState current) {
    if(current instanceof NoCoinState) {
        return Either.right(new HasCoinState());
    }
    return Either.left(new IllegalStateError());
}

18. 状态模式与注解处理器

18.1 生成状态机代码

定义状态机注解:

@StateMachine(states = {"Idle", "Running", "Paused"})
public class PlayerController {
    @Transition(from = "Idle", to = "Running")
    public void play() {}
}

18.2 编译时验证

使用注解处理器确保:

  • 所有状态都有明确定义
  • 转换关系完整
  • 无非法状态路径

19. 状态模式与代码生成

19.1 模板代码生成

使用代码生成工具(如FreeMarker)生成:

  • 状态接口
  • 基础状态类
  • Context骨架代码
  • 单元测试模板

19.2 从DSL生成状态机

定义状态机DSL:

stateMachine("VendingMachine") {
    state("NoCoin") {
        on("insertCoin") transitionTo "HasCoin"
    }
    // 其他状态...
}

20. 状态模式的未来演进

随着Java语言发展,状态模式可能有以下演进方向:

  1. 模式匹配增强 :更简洁的状态处理语法
  2. 值对象支持 :优化不可变状态对象性能
  3. 协程集成 :简化异步状态管理
  4. 记录模式 :解构状态对象更便捷

状态模式作为经典设计模式,在现代Java开发中仍然具有重要价值。通过合理应用和适当改进,它可以有效管理复杂的状态转换逻辑,提升代码的可维护性和扩展性。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐