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

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

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

相关推荐
Victor3565 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
Victor3565 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端
灰子学技术7 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
Gogo8168 小时前
BigInt 与 Number 的爱恨情仇,为何大佬都劝你“能用 Number 就别用 BigInt”?
后端
fuquxiaoguang8 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
毕设源码_廖学姐8 小时前
计算机毕业设计springboot招聘系统网站 基于SpringBoot的在线人才对接平台 SpringBoot驱动的智能求职与招聘服务网
spring boot·后端·课程设计
野犬寒鸦10 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
逍遥德10 小时前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
MX_935911 小时前
Spring的bean工厂后处理器和Bean后处理器
java·后端·spring
程序员泠零澪回家种桔子12 小时前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构