每日知识-设计模式-状态机模式

设计模式确实比较有意思,我之前写过一篇单例模式。

C++中的单例模式

借用一篇文章参考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. 逻辑分支嵌套严重(俗称箭头代码)
  2. 状态流转规则难以维护
  3. 容易遗漏边界条件
  4. 可扩展性差(新增状态需要改动核心逻辑)

有了状态机之后问题就好解决。

核心思想: 将各种具体的状态类抽象出来,将依赖于状态的行为分离到不同的状态类中。

入门例子

假设我们有一个电风扇,它有几个档位(状态):关闭、低速、中速、高速。 它的行为是:每次你按同一个"换挡"按钮,它会切换到下一个状态(循环:关闭 -> 低速 -> 中速 -> 高速 -> 关闭 ...)。

状态模式实现电风扇

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(); // 第五次按:关闭 -> 低速
    }
}

输出结果:

复制代码
从【关闭】状态切换到【低速】状态
从【低速】状态切换到【中速】状态
从【中速】状态切换到【高速】状态
从【高速】状态切换到【关闭】状态
从【关闭】状态切换到【低速】状态

状态模式的优点

  1. 单一职责原则:将与特定状态相关的代码组织到独立的类中,代码更清晰。
  2. 开闭原则:要添加新的状态(比如"涡轮增压"状态)非常容易,你只需创建新的状态类并修改相邻状态的转换逻辑即可,无需修改上下文类或其他现有状态类的大部分代码。
  3. 消除庞大的条件语句 :它避免了在上下文类中使用庞大的条件分支语句(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倍的设计引言:为什么需要状态机?

2 C++中的单例模式

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
大勇前进3 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu3 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile3 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白803 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙3 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端
盖伦发发3 天前
软件工程SOLID 五大设计原则
后端·软件工程