1. 设计模式之状态模式:封装数据的状态流转逻辑
1.1. 介绍
在状态模式(State Pattern)中,类的行为是基于它的状态改变的,这种类型的设计模式属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。负责根据传入的参数,决定这份数据的状态流转到什么状态,同时负责执行那个新状态的代码逻辑,对于有状态的场景,按照面向对象的思想来设计。
状态模式允许对象在内部状态改变时改变其行为,使得对象在不同的状态下有不同的行为表现。通过将每个状态封装成独立的类,可以避免使用大量的条件语句来实现状态切换。
适合场景,数据有状态,状态就一定会流转,从状态1变成状态2,将不同的状态要执行的代码逻辑封装在不同的state类中。
状态模式 vs 策略模式
对比维度 | 状态模式 | 策略模式 |
---|---|---|
目的 | 管理对象的状态流转。 | 动态切换算法。 |
状态控制权 | 状态类或上下文控制转移逻辑。 | 客户端主动选择策略。 |
典型应用 | 订单状态机、游戏角色状态。 | 支付方式、排序算法。 |
应用实例
工作流引擎: 如订单状态(待支付、已发货、已完成)、审批流程。
游戏开发: 角色状态(站立、奔跑、攻击)、NPC行为。
硬件控制: 电梯状态(上行、下行、停止)、自动售货机。
UI组件: 按钮的禁用/启用状态、动画播放状态。
优缺点及建议
优点
1.消除大量条件分支,代码更清晰。
2.状态转换逻辑集中管理。
3.符合开闭原则,易于扩展新状态。
缺点
1.状态类数量可能增多(需合理设计)。
2.上下文需暴露状态设置方法(破坏封装)。
使用建议
1.当对象的行为随状态改变而变化时,考虑使用状态模式。
2.状态模式适用于替代复杂的条件或分支语句。
注意事项
1.简单场景:直接在上下文中实现状态转换(如电灯开关)。
2.复杂场景:将状态转移逻辑分散到具体状态类中(如订单状态机)。
3.避免滥用:状态较少时可能增加不必要的复杂性。
1.2. 实现及相关代码
假设有一个订单业务,牵扯到很多状态的变化,每个状态都有自己的逻辑。
1.2.1. 相关代码
接口及数据对象
public interface State {
void execute();
}
public static class NewState implements State {
public void execute() {
System.out.println("执行销售出库单新建状态的逻辑");
}
}
public static class ApprovingState implements State {
public void execute() {
System.out.println("执行销售出库单待审批状态的逻辑");
}
}
public static class ApprovedState implements State {
public void execute() {
System.out.println("执行销售出库单已审批状态的逻辑");
}
}
public static class FinishedState implements State {
public void execute() {
System.out.println("执行销售出库单已完成状态的逻辑");
}
}
public static class Context {
private State state;
public Context(State state) {
this.state = state;
this.state.execute();
}
/** 可以在执行类中定义状态流转,也可以在各个状态中定义上下关联的状态类;结合自己业务来判定 */
public void execute(int stateType) {
if(stateType == 1) {
this.state = new ApprovingState();
this.state.execute();
} else if(stateType == 2) {
this.state = new ApprovedState();
this.state.execute();
} else if(stateType == 3) {
this.state = new FinishedState();
this.state.execute();
}
}
}
调用方法
public static void main(String[] args) {
Context context = new Context(new NewState());
context.execute(1);
context.execute(2);
context.execute(3);
}