当前位置: 首页 > news >正文

精读 C++20 设计模式:行为型设计模式 — 状态机模式

精读 C++20 设计模式:行为型设计模式 — 状态机模式

前言

状态机(State Machine)是工程里极常见也极重要的工具:当一个对象的行为不仅仅由当前输入决定,而是和“当前状态”强耦合时,状态机让我们把“状态 + 转换规则 + 动作”结构化、可测试、可扩展。状态机有很多实现风格:面向对象的状态驱动(State Pattern)、基于开关(switch/enum)的实现、表驱动(table-driven)、层次化状态机(statecharts/HSMM)、以及事件驱动的异步状态机等等。


什么是状态机(精简定义)

状态机由三部分组成:

  • 状态集(States):对象可能处于的一组离散状态。
  • 事件/输入(Events / Inputs):触发状态转换的外部刺激或内部发生的事情。
  • 转换(Transitions):在特定条件(Guard)下,从一个状态到另一个状态的迁移,同时可以伴随动作(Action)。

状态机通常还定义:entry/exit 动作(进入/离开某状态时执行)、守卫(guard)(条件判断)、并行(orthogonal)状态区域、以及延时/定时器触发等概念。


状态驱动的状态转换(State Pattern 风格)

这种风格把每个状态封装成一个对象(或类),状态对象负责处理事件并决定是否变更到另一个状态。适合状态行为复杂、每个状态需要大量行为代码时,能把逻辑按状态模块化。

下面演示一个 MediaPlayer(媒体播放机) 的状态机,用三种状态:StoppedPlayingPaused。每个状态类实现对 play() / pause() / stop() 的响应,并通过 Context(这里是 MediaPlayer)执行状态切换。

// state_pattern_media.cpp — C++20 演示
#include <iostream>
#include <memory>
#include <string>// 前向声明
class MediaPlayer;// 状态接口(只暴露必要事件)
struct State {virtual ~State() = default;virtual void play(MediaPlayer& ctx) = 0;virtual void pause(MediaPlayer& ctx) = 0;virtual void stop(MediaPlayer& ctx) = 0;virtual std::string name() const = 0;
};// Context:持有状态并委托事件
class MediaPlayer {
public:explicit MediaPlayer(std::shared_ptr<State> s) : state_(std::move(s)) {}void set_state(std::shared_ptr<State> s) {std::cout << "[Context] 状态: " << state_->name() << " -> " << s->name() << "\n";state_ = std::move(s);}void play() { state_->play(*this); }void pause() { state_->pause(*this); }void stop() { state_->stop(*this); }
private:std::shared_ptr<State> state_;
};// 具体状态实现
struct StoppedState : State {void play(MediaPlayer& ctx) override;void pause(MediaPlayer& /*ctx*/) override {std::cout << "[Stopped] pause() 无效\n";}void stop(MediaPlayer& /*ctx*/) override {std::cout << "[Stopped] stop() 已是停止状态\n";}std::string name() const override { return "Stopped"; }
};struct PlayingState : State {void play(MediaPlayer& /*ctx*/) override {std::cout << "[Playing] play() 已在播放中\n";}void pause(MediaPlayer& ctx) override;void stop(MediaPlayer& ctx) override;std::string name() const override { return "Playing"; }
};struct PausedState : State {void play(MediaPlayer& ctx) override;void pause(MediaPlayer& /*ctx*/) override {std::cout << "[Paused] pause() 已暂停\n";}void stop(MediaPlayer& ctx) override;std::string name() const override { return "Paused"; }
};// 状态间切换逻辑(实现放后面)
void StoppedState::play(MediaPlayer& ctx) {std::cout << "[Stopped] 开始播放...\n";ctx.set_state(std::make_shared<PlayingState>());
}
void PlayingState::pause(MediaPlayer& ctx) {std::cout << "[Playing] 暂停播放\n";ctx.set_state(std::make_shared<PausedState>());
}
void PlayingState::stop(MediaPlayer& ctx) {std::cout << "[Playing] 停止播放\n";ctx.set_state(std::make_shared<StoppedState>());
}
void PausedState::play(MediaPlayer& ctx) {std::cout << "[Paused] 恢复播放\n";ctx.set_state(std::make_shared<PlayingState>());
}
void PausedState::stop(MediaPlayer& ctx) {std::cout << "[Paused] 停止并回到初始\n";ctx.set_state(std::make_shared<StoppedState>());
}// 使用示例
int main() {auto stopped = std::make_shared<StoppedState>();MediaPlayer player(stopped);player.play();   // Stopped -> Playingplayer.pause();  // Playing -> Pausedplayer.play();   // Paused -> Playingplayer.stop();   // Playing -> Stoppedplayer.pause();  // 无效return 0;
}

优点(State Pattern)

  • 每个状态的逻辑局部化,便于维护、单元测试与扩展。
  • 增加新状态无需改动大量 switch-case,开放/封闭性好。
  • 可以在状态对象中保存状态相关数据(如果需要)。

缺点

  • 如果状态很多,会产生大量类,增加复杂度(但可以用单例或共享实例减少开销)。
  • 状态对象之间切换需要 Context 提供切换接口,设计上要小心避免循环依赖。

如何设计状态机

设计状态机不是随手写个 enum 就完事,下面是实战建议:

  1. 明确领域上的“状态”与“事件”
    • 列出对象可能的状态(名词)。
    • 列出能触发变化的事件(动词/消息)。
  2. 画出状态图(最重要)
    • 把状态画成节点,事件作为边(标注 guard/动作/entry/exit)。
    • 标注 entry/exit 以及延时触发 (timer) 的边。
  3. 区分转换类型
    • 外部转换(leave + enter):先执行 exit,再 transition,再 entry。
    • 内部转换(stay + action):在同一状态内响应事件,不触发 exit/entry。
  4. 定义 Guard 与 Actions
    • Guard:条件判断(例如用户权限、资源可用性)。
    • Action:转换时需要执行的副作用(日志、网络调用、排队等)。
  5. 考虑并行(Orthogonal)区域
    • 对于复杂对象,可能需要多个互不干扰的子状态机(例如播放器既有播放状态,也有网络状态)。把它们做成并行 region,会比把所有组合列举更清晰。
  6. Entry / Exit handlers
    • 把资源分配/释放放到 entry/exit,可以避免状态切换时资源泄漏。
  7. 测试策略
    • 对每一条边写单元测试(从状态A触发事件E应该进入状态B并产生动作X)。
    • 使用表驱动测试(state,event -> expected_state, expected_action)。
  8. 性能与持久化
    • 若状态机在高频路径,prefer switch/enum 或零分配实现;若可维护性优先用 State Pattern。
    • 如需持久化(checkpoint),只序列化当前 state id + 必要上下文。

基于开关的状态机(switch / enum) + 扩展

基于 enum + switch 的实现是最直观、也最常见的做法:把状态放在一个枚举里,接收事件时使用 switch(current_state) 跳转。适用于状态相对较少、转换逻辑简单、性能敏感的场景。

下面是 交通信号灯(Traffic Light) 的简单示例,包含定时转换与紧急事件。

// switch_state_traffic.cpp
#include <iostream>
#include <chrono>
#include <thread>enum class LightState { Red, Green, Yellow };
enum class Event { Timer, Emergency };struct TrafficLight {LightState state = LightState::Red;int timer_ms = 0;void on_event(Event ev) {switch (state) {case LightState::Red:if (ev == Event::Timer) {std::cout << "Red -> Green\n";state = LightState::Green;} else if (ev == Event::Emergency) {std::cout << "Red + Emergency -> Flashing (保持Red)\n";// 非常简化的策略}break;case LightState::Green:if (ev == Event::Timer) {std::cout << "Green -> Yellow\n";state = LightState::Yellow;} else if (ev == Event::Emergency) {std::cout << "Green + Emergency -> 切换到 Red\n";state = LightState::Red;}break;case LightState::Yellow:if (ev == Event::Timer) {std::cout << "Yellow -> Red\n";state = LightState::Red;}break;}}
};int main() {TrafficLight tl;// 模拟定时器触发tl.on_event(Event::Timer); // Red -> Greentl.on_event(Event::Timer); // Green -> Yellowtl.on_event(Event::Emergency); // Yellow 无变更tl.on_event(Event::Timer); // Yellow -> Redreturn 0;
}

优点(switch-based)

  • 直观、简单、性能高(没有虚调用开销)。
  • 易于集中查看所有转换(在一个 switch 里)。

缺点

  • 当状态或事件变多时 switch 会变得臃肿(逻辑散落、难以维护)。
  • 不利于按状态封装复杂行为,扩展性差(每新增状态/事件都要改 switch)。

扩展:把基于开关的实现变得更工程化

1) 表驱动(Transition Table)

把转换写成数据(表格)而非代码,支持热插拔策略、容易测试。示例结构:

struct Transition {State from;Event  on;std::function<bool()> guard;   // 可选std::function<void()> action;  // 可选State to;
};

运行时逐项匹配 from==current && on==event && (guard()==true),执行 action 并切换到 to。利于把复杂规则序列化到配置文件。

std::variant + std::visit 表示状态

std::variant<StateA, StateB, StateC> 表示状态,配合 std::visit 实现分发,这样能既保留类型安全又避免虚函数开销。

层次化状态机(Hierarchical State Machines / Statecharts)

支持子状态与父状态:若事件在子状态未处理,会向上冒泡到父状态。这能消除重复转换并表达“通用行为在父状态”,常见于 UML 状态图或 SCXML。

并行区域(Orthogonal Regions)

当对象有多个独立属性需要并行状态时,用多个子状态机并行执行,比把所有组合穷举为状态集合更清晰。

超时/定时器与异步事件

状态机常配合定时器(例如:在某状态等待 N 秒后自动切换)或外部异步事件(网络返回、IO 完成)。工程实现通常需要事件队列、工作线程与非阻塞处理。

使用现成库(若项目复杂)

当状态机非常复杂(层次化、多并行区、可视化调试、保存/恢复)时,可以考虑成熟库(如 Boost.SML、Boost.Statechart、SML 或商用引擎) — 在大工程里这些库能显著降低实现与测试成本(选用前评估学习成本与运行时特性)。


总结

我们试图解决什么问题?
  • 状态依赖行为多:对象行为不仅依赖于当前输入,还强依赖于“当前状态”。
  • 交叉条件复杂:不同状态下相同事件需不同处理;如果把逻辑散落在多个 if/switch 中,难以理解与维护。
  • 需要可扩展、可测试、可观察的行为模型:特别是在并发、异步或嵌入式等领域,明确状态与转换能降低 bugs。
我们如何解决问题?
  • 面向对象的状态驱动(State Pattern):把状态作为对象,封装行为,实现开闭原则;适合状态行为复杂时。
  • 基于开关(enum + switch):直观、高效,适合状态少、性能敏感且逻辑简单的场景。
  • 表驱动 / 数据驱动:把转换抽象为数据,便于配置、测试、序列化。
  • 层次化/并行化状态机:解决状态组合爆炸问题,提高复用性与表达力。
  • 引入事件队列与定时器:处理异步/延时转换,保证系统稳健运行。
方案优点与缺点(对比)
  • State Pattern(面向对象)
    • 优点:模块化、易维护、易扩展、易单元测试。
    • 缺点:类数量增多、若状态非常多实现开销(内存、复杂度)上升。
  • Switch / Enum(基于开关)
    • 优点:实现简单、性能好、易读(小规模)。
    • 缺点:不利于扩展,逻辑会散落、难以模块化;当状态/事件增多会臃肿。
  • Table-driven
    • 优点:规则集中、易测试、便于序列化与动态配置。
    • 缺点:对复杂动作/guard 表达力有限,需要配合函数对象;调试时需良好可观测性。
  • Hierarchical / Parallel States
    • 优点:表达力强、减少重复、模型更贴合真实系统(例如 GUI、嵌入式设备)。
    • 缺点:实现与调试复杂;可能需借助成熟库。
http://www.dtcms.com/a/427084.html

相关文章:

  • 多周期路径约束
  • Webpack配置之path.join、path.resolve和__dirname详解
  • vue打包优化方案都有哪些?
  • Golang 中的字符串:常见错误和最佳实践
  • 花生壳建设网站怎样做网络营销推广
  • 【Rust GUI开发入门】编写一个本地音乐播放器(8. 从文件中提取歌曲元信息)
  • 国内个人网站建设贾汪城乡建设局网站
  • CentOS二进制安装包方式部署K8S集群之系统初始化
  • Spring Boot 缓存集成实践
  • 力扣Hot100--21.合并两个有序链表
  • 网络安全和NLP、CV是并行的应用吗?
  • 如何做好一个企业网站专门做图片的网站
  • 网页设计网站wordpress公告栏插件
  • C++ 位运算 高频面试考点 力扣 371. 两整数之和 题解 每日一题
  • 网络安全常见敏感目录字典
  • React学习(三)--- 组件化开发编写css
  • 设计模式(C++)详解——观察者模式(Observer)(1)
  • 网站建设报表明细新手做网站看什么书
  • 微课网站开发如何查看网站域名
  • Spring工程 生成表和mapper文件
  • 服装培训网站建设网站图片切换
  • Python爬虫实战:获取丁香人才网招聘信息与数据分析
  • 光学转镜最小长度计算模型:基于视场角与有效口径的匹配算法
  • 汉子由来 外国人做的网站网页设计的尺寸是指
  • 智能驱动与合规双赢:2025年企业DevOps平台选型深度解析
  • 2025年,如何选择Python Web框架:Django, Flask还是FastAPI?
  • FLASK与JAVA的多文件互传(多文件互传亲测)
  • 蓝牙音箱的技术演进:从便捷到高保真的音频革命
  • 打破信息孤岛,构建统一视界:视频融合平台EasyCVR在智慧校园建设中的核心作用
  • 计算机应用技术网站开发基础知识网店推广平台