状态模式(State Pattern)

C++ 状态模式(State Pattern)

一、模式概述

状态模式(State Pattern)属于行为型设计模式

核心思想

将对象的不同状态 分别封装为独立类,对象行为随内部状态改变而自动变化,对外表现如同修改了对象所属类;彻底替代传统大量 if-else / switch 分支判断,让状态逻辑解耦、职责清晰。

适用场景

  1. 对象行为依赖自身状态,运行时状态改变则行为随之改变;
  2. 业务中存在大量基于状态的条件判断代码,分支臃肿、难以维护;
  3. 状态数量固定、状态转换规则明确,需要扩展新状态;
  4. 典型业务:电梯状态、订单流程、网络连接、游戏角色、设备运行状态机等。

二、核心角色

状态模式由三类核心角色组成,各司其职:

角色 名称 职责说明
抽象状态 State 定义所有状态统一的行为接口,声明状态处理方法,通常为纯虚类
具体状态 ConcreteStateX 继承抽象状态,实现当前状态独有行为;负责触发状态跳转
上下文 Context 持有当前状态对象;对外提供统一调用入口;提供状态切换接口,串联整个状态流程

角色协作流程

  1. 客户端调用上下文的对外接口;
  2. 上下文将请求委托给当前具体状态对象处理;
  3. 具体状态执行自身逻辑,并根据业务规则修改上下文的状态;
  4. 下次请求会交由新状态处理,实现行为自动切换。

三、模式优缺点

优点

  1. 消除分支语句 :剥离大量 if-else/switch,代码结构更整洁;
  2. 符合开闭原则:新增状态只需新增具体状态类,无需修改原有代码;
  3. 状态逻辑隔离:每个状态的行为、转换逻辑内聚在对应类中,职责单一,易维护;
  4. 状态转换规范化:转换规则统一管理,流程清晰可控。

缺点

  1. 类数量膨胀:状态越多,对应的具体状态类就越多,增加代码体量;
  2. 流程分散:状态跳转逻辑分散在各个状态类中,整体状态流转流程阅读难度提升;
  3. 过度设计:简单状态(仅2~3个状态、逻辑简单)使用该模式会增加复杂度。

四、C++ 基础实现(标准写法)

1. 前置说明

  • 使用纯虚类作为抽象状态基类;
  • 采用原始指针演示基础逻辑,后文补充智能指针工程写法;
  • 类之间存在交叉引用,使用前向声明解决头文件循环依赖。

2. 完整代码示例

cpp 复制代码
#include <iostream>
using namespace std;

// 前向声明上下文类
class Context;

// ===================== 抽象状态类 State =====================
class State
{
public:
    // 纯虚函数:状态处理行为,参数为上下文,用于状态切换
    virtual void handle(Context* context) = 0;
    virtual ~State() = default; // 虚析构,保证多态析构安全
};

// ===================== 具体状态类 A =====================
class ConcreteStateA : public State
{
public:
    void handle(Context* context) override;
};

// ===================== 具体状态类 B =====================
class ConcreteStateB : public State
{
public:
    void handle(Context* context) override;
};

// ===================== 上下文类 Context =====================
class Context
{
private:
    State* m_currentState; // 持有当前状态对象
public:
    // 构造函数:初始化默认状态
    Context(State* state) : m_currentState(state) {}

    // 设置/切换状态
    void setState(State* state)
    {
        m_currentState = state;
    }

    // 对外统一请求接口
    void request()
    {
        if (m_currentState)
        {
            m_currentState->handle(this); // 委托给当前状态处理
        }
    }
};

// 状态A 实现逻辑:执行行为,并切换到状态B
void ConcreteStateA::handle(Context* context)
{
    cout << "当前处于【状态A】,执行状态A逻辑" << endl;
    // 状态跳转:切换为状态B
    context->setState(new ConcreteStateB());
}

// 状态B 实现逻辑:执行行为,并切换到状态A
void ConcreteStateB::handle(Context* context)
{
    cout << "当前处于【状态B】,执行状态B逻辑" << endl;
    // 状态跳转:切换为状态A
    context->setState(new ConcreteStateA());
}

// ===================== 客户端测试 =====================
int main()
{
    // 初始化上下文,默认状态为A
    Context ctx(new ConcreteStateA());

    // 多次调用,观察状态自动切换
    ctx.request();
    ctx.request();
    ctx.request();

    return 0;
}

3. 运行结果

复制代码
当前处于【状态A】,执行状态A逻辑
当前处于【状态B】,执行状态B逻辑
当前处于【状态A】,执行状态A逻辑

五、C++ 工程级优化(智能指针版)

原始指针容易造成内存泄漏、野指针 ,正式项目推荐使用 std::unique_ptr 管理状态对象。

优化后代码

cpp 复制代码
#include <iostream>
#include <memory>
using namespace std;

class Context;

// 抽象状态
class State
{
public:
    virtual void handle(Context* context) = 0;
    virtual ~State() = default;
};

// 具体状态A
class ConcreteStateA : public State
{
public:
    void handle(Context* context) override;
};

// 具体状态B
class ConcreteStateB : public State
{
public:
    void handle(Context* context) override;
};

// 上下文(使用 unique_ptr 管理状态)
class Context
{
private:
    unique_ptr<State> m_currentState;
public:
    explicit Context(unique_ptr<State> state) : m_currentState(move(state)) {}

    void setState(unique_ptr<State> state)
    {
        m_currentState = move(state);
    }

    void request()
    {
        if (m_currentState)
            m_currentState->handle(this);
    }
};

void ConcreteStateA::handle(Context* context)
{
    cout << "当前处于【状态A】,执行逻辑" << endl;
    context->setState(make_unique<ConcreteStateB>());
}

void ConcreteStateB::handle(Context* context)
{
    cout << "当前处于【状态B】,执行逻辑" << endl;
    context->setState(make_unique<ConcreteStateA>());
}

// 客户端
int main()
{
    Context ctx(make_unique<ConcreteStateA>());
    ctx.request();
    ctx.request();
    ctx.request();
    return 0;
}

优势 :智能指针自动释放内存,无需手动 delete,规避内存泄漏。

六、两种状态切换方式

在实际开发中,状态切换分为两种模式:

1. 状态类内部控制跳转(常用)

  • 跳转逻辑写在 ConcreteState::handle 中;
  • 优点:状态自管理,内聚性强;
  • 缺点:跳转逻辑分散。

2. 上下文统一控制跳转

  • 所有状态转换规则集中写在 Context 中;
  • 优点:整体状态流转一目了然,便于梳理完整流程;
  • 缺点:上下文会随状态增多变得臃肿。

七、状态模式 vs 策略模式(易混区分)

两者结构相似,均封装行为、委托执行,核心区别在行为目的与切换逻辑

对比维度 状态模式 策略模式
核心目的 处理对象状态变化,行为随状态自动改变 封装多种算法/策略,按需替换算法
切换触发 内部状态流转,自动切换 外部客户端主动选择,手动切换
关系 状态之间存在流转、依赖关系 各个策略相互独立,无先后流转关系
典型场景 状态机、订单、电梯、设备状态 排序算法、支付方式、加密算法

八、使用注意事项

  1. 控制状态数量:状态过多会产生大量子类,若状态超过10个,可考虑改用配置表+枚举实现状态机;
  2. 循环依赖处理ContextState 互相引用,必须使用前向声明
  3. 析构安全 :抽象状态基类必须提供虚析构函数,防止多态析构内存泄漏;
  4. 慎用场景:简单两状态、固定逻辑,不建议强行使用,避免过度设计;
  5. 多线程场景 :多线程下需对 m_currentState 加锁,保证状态切换线程安全。

九、总结

  1. 本质:把状态变成类,用多态替代分支判断
  2. 核心三角色:抽象状态、具体状态、上下文;
  3. C++ 开发优先使用智能指针管理状态对象,保证内存安全;
  4. 适合复杂状态机场景,简单场景按需取舍。
相关推荐
lazy熊9 小时前
AI编程实战9:用 Codex 把一段重复 UI 抽成组件
ui·ai编程
前端不太难9 小时前
鸿蒙 PC 为什么需要新的组件体系?
华为·状态模式·harmonyos
晓杰'10 小时前
从0到1实现Balatro游戏后端(4):玩家手牌操作(出牌 / 弃牌 / 补牌)与状态流转设计
后端·websocket·typescript·node.js·状态模式·项目实战·nestjs
dinl_vin10 小时前
FastAPI 系列 ·(九):中间件与错误处理:让服务更健壮
中间件·状态模式·fastapi
light blue bird12 小时前
可更新组装工序资源图表功能组件
开发语言·前端·jvm·.net·状态模式
小许同学记录成长13 小时前
频谱分析仪 UI 自定义绘制
ui·架构
王翼鹏1 天前
利用AI根据设计图开发页面总结
ui·ai
ZC跨境爬虫1 天前
模块化烹饪小程序开发日记 Day7:(菜谱详情接口开发与JSON数据读取全流程)
前端·javascript·css·ui·微信小程序·json
王翼鹏1 天前
claude code 使用ui-spec 命令生成UI设计说明
ui·自定义命令·claude code