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

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

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-elseswitch-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")

相关推荐
回家路上绕了弯2 小时前
ClickHouse 深度解析:从核心特性到实战应用,解锁 OLAP 领域新势能
数据库·后端
xiaok3 小时前
本地用VScode的Live Server监听5500访问页面,ubuntu上不需要在配置5500
后端
雨绸缪3 小时前
ABAP 时间戳
后端
m0_480502643 小时前
Rust 登堂 之 函数式编程(三)
开发语言·后端·rust
艾醒3 小时前
大模型面试题剖析:大模型微调与训练硬件成本计算
人工智能·后端·算法
用户298698530143 小时前
如何使用 Spire.Doc 在 C# 中创建、写入和读取 Word 文档?
后端
林太白3 小时前
项目中的层级模块到底如何做接口
前端·后端·node.js
一枚小小程序员哈4 小时前
基于Android的车位预售预租APP/基于Android的车位租赁系统APP/基于Android的车位管理系统APP
android·spring boot·后端·struts·spring·java-ee·maven
二闹4 小时前
从@Transactional失效场景到传播行为原理
java·后端