C++状态模式状态行为切换
C++状态模式:让对象“自己决定下一步怎么活”
写过状态机代码的人,大概都踩过这样的坑:用一长串 if-else if 判断当前状态,再调一堆 switch 处理行为;改一个状态逻辑,得翻三四个文件,加个新状态?光是补 case 和更新状态变量就手抖。更别提多线程下状态变量被意外修改,导致对象“精神分裂”——前一秒还在播放音乐,后一秒突然开始下载文件。
这不是设计问题,是状态责任没划清。C++里,状态模式不是教科书里的概念标本,而是一把能剪开混乱逻辑的剪刀。
核心就一条:把每个状态封装成独立类,让状态自己管自己的行为和转移规则。对象本身不再操心“我现在是谁”,只负责“把请求转给当前状态”。
比如做一个简易音频播放器。它有三种典型状态:Stopped、Playing、Paused。传统写法里,play() 方法可能这样:
void AudioPlayer::play() {
if (state == STOPPED) {
startPlayback();
state = PLAYING;
} else if (state == PAUSED) {
resumePlayback();
state = PLAYING;
} // ……还有更多分支
}
问题在哪?
- 行为逻辑和状态判断混在一起;
- 每次新增状态(比如
Buffering),所有方法都要改; state成员变量成了全局靶子,谁都能改,没人负责校验合法性。
状态模式的解法很干脆:让状态自己回答“我该怎么做”。
先定义状态接口:
struct PlayerState {
virtual ~PlayerState() = default;
virtual void play(AudioPlayer& player) = 0;
virtual void pause(AudioPlayer& player) = 0;
virtual void stop(AudioPlayer& player) = 0;
};
注意:每个方法都接收 AudioPlayer& 引用。这不是为了偷懒传参,而是让状态在必要时能主动修改播放器的内部状态(比如切换 current_state_)或触发其他动作(如启动解码线程)。这是很多教程忽略的关键细节——状态不是哑巴,它得有“动手能力”。
接着实现具体状态:
struct PlayingState : PlayerState {
void play(AudioPlayer& player) override {
// 已在播放,什么也不做,或抛异常/日志
}
void pause(AudioPlayer& player) override {
player.current_state_ = std::make_unique<PausedState>();
player.suspendPlayback(); // 真实暂停操作
}
void stop(AudioPlayer& player) override {
player.current_state_ = std::make_unique<StoppedState>();
player.clearPlayback();
}
};
AudioPlayer 本身变得极其干净:
class AudioPlayer {
std::unique_ptr<PlayerState> current_state_;
public:
AudioPlayer() : current_state_(std::make_unique<StoppedState>()) {}
void play() { current_state_->play(*this); }
void pause() { current_state_->pause(*this); }
void stop() { current_state_->stop(*this); }
};
看到没?AudioPlayer 不再保存 enum State,不写任何条件判断,甚至不知道自己有几种状态。它只做一件事:委托。
这带来三个实在好处:
- 新增状态零侵入:加
BufferingState?只写新类,改构造函数初始值,其他地方不动; - 状态逻辑彻底隔离:
PausedState::play()里想启动后台预加载,还是发通知,都关别人什么事; - 测试成本直线下降:每个状态类可单独单元测试,不用模拟整个播放器上下文。
但真实项目里,状态切换常带条件。比如 PausedState 下按快进键,要先 stop() 再 play(),而不是直接跳到 PlayingState。这时候,状态类内部可以触发多次委托:
void PausedState::seekForward(AudioPlayer& player) {
player.stop(); // 委托给自己,触发状态变为 Stopped
player.play(); // 再委托,由 StoppedState 决定下一步
}
这种“状态链式响应”,比硬编码 player.current_state_ = std::make_unique<PlayingState>() 更安全——它尊重了每个状态的自治权。
还有一点实战经验:避免在状态类里持有播放器数据的裸指针。用引用传递足够,既轻量又明确所有权归属。若需访问播放器私有成员(比如缓冲区指针),可在 AudioPlayer 中提供受控的 friend 接口,或用 get_buffer() 这类只读方法暴露必要信息。控制好边界,状态类才不会变成第二个上帝对象。
最后提醒一个易错点:状态对象的生命周期。用 std::unique_ptr 管理最稳妥。如果状态需要共享(比如多个播放器共用同一套空闲逻辑),再考虑 std::shared_ptr 或单例工厂——但别一上来就上共享,多数时候是过度设计。
状态模式不是银弹,它适合状态数量稳定、行为差异明显、且状态间转移规则清晰的场景。如果你的状态只有两种,且转移逻辑简单,if-else 可能更直白。模式的价值,永远在于它帮你提前封住未来三个月的修改裂口,而不是第一天就炫技。
写完 StoppedState 的最后一个 },你会觉得:原来让对象“自己决定下一步怎么活”,不是玄学,只是把责任归还给该负责的那一方。


还没有评论,来说两句吧...