设计模式确实比较有意思,我之前写过一篇单例模式。
借用一篇文章[参考1]的例子,我们先来描述一下没有状态机的笨拙方案。
在后台系统开发中,我们经常需要处理对象的状态流转问题:订单从"待支付"到"已支付"再到"已发货",工单系统从"打开"到"处理中"再到"解决",这些场景都涉及状态管理。
如果不使用状态机设计,我们可能会写出这样的面条式代码:
vbnet
func HandleOrderEvent(order *Order, event Event) error {
if order.Status == "待支付" {
if event.Type == "支付成功" {
order.Status = "已支付"
// 执行支付成功逻辑...
} else if event.Type == "取消订单" {
order.Status = "已取消"
// 执行取消逻辑...
} else {
return errors.New("非法事件")
}
} else if order.Status == "已支付" {
if event.Type == "发货" {
order.Status = "已发货"
// 执行发货逻辑...
}
// 更多else if...
}
// 更多else if...
}
这种代码存在几个致命问题:
- 逻辑分支嵌套严重(俗称箭头代码)
- 状态流转规则难以维护
- 容易遗漏边界条件
- 可扩展性差(新增状态需要改动核心逻辑)
有了状态机之后问题就好解决。
核心思想: 将各种具体的状态类抽象出来,将依赖于状态的行为分离到不同的状态类中。
入门例子
假设我们有一个电风扇,它有几个档位(状态):关闭
、低速
、中速
、高速
。 它的行为是:每次你按同一个"换挡"按钮,它会切换到下一个状态(循环:关闭 -> 低速 -> 中速 -> 高速 -> 关闭 ...)。
状态模式实现电风扇
1. 首先定义状态接口
所有具体状态类都需要实现这个接口。它定义了所有可能的行为(在这个例子里,行为就是按按钮 pressButton
)。
java
// 状态接口
public interface FanState {
void pressButton(Fan fan);
}
2. 实现各个具体状态类
每个状态类都知道自己按下按钮后,风扇应该切换到什么状态。
关闭状态
java
public class OffState implements FanState {
@Override
public void pressButton(Fan fan) {
System.out.println("从【关闭】状态切换到【低速】状态");
fan.setCurrentState(new LowState());
}
}
低速状态
java
public class LowState implements FanState {
@Override
public void pressButton(Fan fan) {
System.out.println("从【低速】状态切换到【中速】状态");
fan.setCurrentState(new MediumState());
}
}
中速状态
java
public class MediumState implements FanState {
@Override
public void pressButton(Fan fan) {
System.out.println("从【中速】状态切换到【高速】状态");
fan.setCurrentState(new HighState());
}
}
高速状态
java
public class HighState implements FanState {
@Override
public void pressButton(Fan fan) {
System.out.println("从【高速】状态切换到【关闭】状态");
fan.setCurrentState(new OffState());
}
}
3. 定义上下文类(Context)------ 电风扇
这个类持有当前状态的引用,并将来自外部的请求(按按钮)委托给当前状态对象处理。
java
// 上下文类 - 电风扇
public class Fan {
private FanState currentState;
// 构造函数,初始状态为关闭
public Fan() {
this.currentState = new OffState();
}
// 设置当前状态
public void setCurrentState(FanState state) {
this.currentState = state;
}
// 用户行为:按下按钮
// 它将实际工作委托给当前的状态对象
public void pressButton() {
currentState.pressButton(this);
}
}
4. 测试代码
java
public class Main {
public static void main(String[] args) {
Fan fan = new Fan(); // 风扇初始为【关闭】状态
fan.pressButton(); // 第一次按:关闭 -> 低速
fan.pressButton(); // 第二次按:低速 -> 中速
fan.pressButton(); // 第三次按:中速 -> 高速
fan.pressButton(); // 第四次按:高速 -> 关闭
fan.pressButton(); // 第五次按:关闭 -> 低速
}
}
输出结果:
从【关闭】状态切换到【低速】状态
从【低速】状态切换到【中速】状态
从【中速】状态切换到【高速】状态
从【高速】状态切换到【关闭】状态
从【关闭】状态切换到【低速】状态
状态模式的优点
- 单一职责原则:将与特定状态相关的代码组织到独立的类中,代码更清晰。
- 开闭原则:要添加新的状态(比如"涡轮增压"状态)非常容易,你只需创建新的状态类并修改相邻状态的转换逻辑即可,无需修改上下文类或其他现有状态类的大部分代码。
- 消除庞大的条件语句 :它避免了在上下文类中使用庞大的条件分支语句(
if-else
或switch-case
),使代码更易于维护和理解。
状态模式非常适合当一个对象的行为取决于它的状态,并且它需要在运行时根据状态改变行为,同时状态数量又比较多的情况。它将状态逻辑分散到不同的类中,使得代码结构清晰,易于扩展。
再抽象一下,所谓的风扇就是 对应 Context类,标识上下文环境,所谓的档位,对应State类,表示不同的状态, 最简单的类关系表示如下所示:
scala
class State{}
class StateX extends State{
public void switch(Context context){ // 传递一个环境上下文
// 执行特定的动作,然后切换到下一个状态
context.setCurrentState(new StateY("stateX的下一个状态"));
}
}
class StateY extends State{ .... }
class Context{
private State currentState; // 拥有关系
public void setCurrentState(State newState){
currentState = newState;
}
public void move(){
currentState.switch(this);
}
}
参考
1\] [状态机设计:比if-else优雅100倍的设计引言:为什么需要状态机?](https://juejin.cn/post/7513752860162129960 "https://juejin.cn/post/7513752860162129960") \[2\] [C++中的单例模式](https://juejin.cn/post/7080911756247171079 "https://juejin.cn/post/7080911756247171079")