状态模式 Java 17 实战:3种状态转换策略与自动售货机代码重构
状态模式 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) {
// 处理重复投币...
}
// 更多条件判断...
}
// 其他方法...
}
主要问题 :
- 使用条件语句而非多态
- 状态转换逻辑分散
- 违反开闭原则
- 难以扩展新状态
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 性能优化技巧
状态模式可能引入的性能问题及解决方案:
-
状态对象创建开销 :
// 使用对象池或享元模式优化 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); } }); } } -
状态转换原子性保证 :
// 使用原子引用确保线程安全 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 最佳实践清单
-
状态设计原则 :
- 每个状态类只关注自身行为
- 状态转换逻辑要么集中在Context,要么分散在各状态
- 避免"上帝状态"知道太多其他状态
-
性能考量 :
- 考虑状态对象的创建成本
- 高频状态转换场景注意线程安全
- 复杂状态可考虑备忘录模式保存/恢复
-
可维护性建议 :
- 为状态转换添加日志记录
- 使用状态模式生成器简化创建
- 考虑可视化状态转换图
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);
}
// 其他方法...
}
关键设计决策 :
- 使用sealed接口限制状态类型
- 默认方法提供安全默认行为
- 不可变状态对象
- 清晰的上下文接口
8. 常见陷阱与解决方案
8.1 状态爆炸问题
当状态过多时,解决方案:
- 使用层次状态机(HSM)
- 组合状态模式
- 考虑转为状态表驱动
8.2 循环依赖问题
状态与Context相互引用时的解决方案:
// 使用接口隔离
public interface VendingContext {
void changeState(VendingState newState);
// 仅暴露必要方法
}
// 具体机器实现接口
public class VendingMachine implements VendingContext {
// 实现细节...
}
8.3 状态持久化方案
将状态保存到数据库的几种方式:
-
枚举方式 :
@Entity public class MachineState { @Enumerated(EnumType.STRING) private StateEnum currentState; } -
类型鉴别器方式 :
@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 测试指导原则
- 每个状态类应独立测试
- 测试状态转换边界条件
- 模拟非法状态转换
- 验证状态行为副作用
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语言发展,状态模式可能有以下演进方向:
- 模式匹配增强 :更简洁的状态处理语法
- 值对象支持 :优化不可变状态对象性能
- 协程集成 :简化异步状态管理
- 记录模式 :解构状态对象更便捷
状态模式作为经典设计模式,在现代Java开发中仍然具有重要价值。通过合理应用和适当改进,它可以有效管理复杂的状态转换逻辑,提升代码的可维护性和扩展性。
更多推荐


所有评论(0)