C++ · 设计模式 (状态变化=State<状态机>)
1. 概述
GoF分类
状态模式属于行为型设计模式,在《设计模式:可复用面向对象软件的基础》中被归类为对象行为模式。
官方定义
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
核心目的
状态模式的主要目的是让对象在不同的状态下表现出不同的行为,而无需在代码中使用大量的条件判断语句。
解决的问题
当对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,状态模式可以:
-
消除庞大的条件分支语句
-
使状态转换显式化
-
提高代码的可维护性和扩展性
cpp典型应用价值
-
游戏开发中的角色状态管理(站立、行走、跳跃等)
-
网络协议实现中的连接状态管理
-
工作流系统中的流程状态控制
-
UI系统中的界面状态切换
关键特征
-
将状态封装为独立的对象
-
将状态相关的行为局部化到各自的状态类中
-
状态转换由状态对象或上下文控制
-
符合开闭原则,易于添加新状态
2. 核心思想
设计哲学
状态模式基于两个核心原则:
-
单一职责原则:每个状态类只负责一个特定状态下的行为
-
开闭原则:新增状态不需要修改现有代码
面向对象原则应用
-
封装变化:将状态相关的行为封装在各自的状态类中
-
多态:通过状态接口实现运行时多态
-
松耦合:上下文类不依赖具体状态类,只依赖抽象状态接口
cpp实现特殊考虑
-
资源管理:使用智能指针管理状态对象生命周期
-
异常安全:确保状态转换时的异常安全
-
性能:避免频繁创建销毁状态对象,可考虑对象池
-
线程安全:在多线程环境中保护状态转换
工作流程示意图
[Client] --> [Context] --> [State]
| |
v v
[request()] [handle()]
|
v
[state->handle()]
|
v
[可能改变当前状态]
3. 适用场景
应该使用状态模式的场景
场景1:TCP连接状态管理
// 状态接口
class TCPState {
public:
virtual void open(TCPConnection*) = 0;
virtual void close(TCPConnection*) = 0;
// ...
};
// 上下文
class TCPConnection {
std::unique_ptr<TCPState> state_;
public:
void setState(std::unique_ptr<TCPState> state) {
state_ = std::move(state);
}
// 委托给当前状态
void open() { state_->open(this); }
void close() { state_->close(this); }
};
场景2:电梯控制系统
class ElevatorState {
public:
virtual void moveToFloor(int) = 0;
virtual void openDoor() = 0;
// ...
};
class Elevator {
std::unique_ptr<ElevatorState> state_;
int currentFloor_;
public:
void changeState(std::unique_ptr<ElevatorState> newState) {
state_ = std::move(newState);
}
void requestFloor(int floor) {
state_->moveToFloor(floor);
}
};
场景3:订单状态处理
class OrderState {
public:
virtual void cancel(Order*) = 0;
virtual void ship(Order*) = 0;
// ...
};
class Order {
std::unique_ptr<OrderState> state_;
public:
void setState(std::unique_ptr<OrderState> state) {
state_ = std::move(state);
}
void cancel() { state_->cancel(this); }
};
不适合使用状态模式的场景
场景1:简单状态转换
当状态数量很少(2-3个)且行为简单时,使用状态模式可能过度设计。
替代方案:使用枚举和switch语句
场景2:状态频繁变化的高性能场景
当状态转换非常频繁且性能敏感时,状态模式的对象创建开销可能成为瓶颈。
替代方案:使用状态表或位掩码等更轻量级方案
场景3:状态逻辑高度耦合
当不同状态的行为高度相似或相互依赖时,状态模式可能增加不必要的复杂性。
替代方案:考虑策略模式或模板方法模式
4. UML类图
5. 结构
模式参与者详细分析
Context(上下文)
-
角色:定义客户端感兴趣的接口
-
职责:
-
维护一个ConcreteState子类的实例
-
将状态相关的请求委托给当前状态对象
-
提供方法用于改变当前状态
-
State(状态接口)
-
角色:定义状态的通用接口
-
职责:
-
声明状态特定的行为方法
-
可包含默认实现或纯虚函数
-
ConcreteState(具体状态)
-
角色:实现特定状态的行为
-
职责:
-
实现与状态相关的行为
-
可触发状态转换(通过Context)
-
可访问Context以获取所需信息
-
协作流程
-
Context接收到客户端请求
-
Context将请求委托给当前状态对象
-
状态对象处理请求,可能:
-
执行状态特定的行为
-
修改Context的状态(通过changeState)
- 返回处理结果给客户端
6. 代码示例
#include <iostream>
#include <memory>
#include <string>
// 前向声明
class Player;
// 状态接口
class PlayerState {
public:
virtual ~PlayerState() = default;
virtual void handleInput(Player& player, const std::string& input) = 0;
virtual void update(Player& player) = 0;
virtual std::string getDescription() const = 0;
};
// 具体状态:站立状态
class StandingState : public PlayerState {
public:
void handleInput(Player& player, const std::string& input) override;
void update(Player& player) override {
std::cout << "Player is standing firmly\n";
}
std::string getDescription() const override {
return "Standing";
}
};
// 具体状态:跳跃状态
class JumpingState : public PlayerState {
public:
void handleInput(Player& player, const std::string& input) override;
void update(Player& player) override {
std::cout << "Player is in the air\n";
}
std::string getDescription() const override {
return "Jumping";
}
};
// 具体状态:奔跑状态
class RunningState : public PlayerState {
public:
void handleInput(Player& player, const std::string& input) override;
void update(Player& player) override {
std::cout << "Player is running fast\n";
}
std::string getDescription() const override {
return "Running";
}
};
// 上下文:玩家类
class Player {
std::unique_ptr<PlayerState> state_;
public:
Player() : state_(std::make_unique<StandingState>()) {}
void setState(std::unique_ptr<PlayerState> newState) {
state_ = std::move(newState);
std::cout << "Player state changed to: " << state_->getDescription() << "\n";
}
void handleInput(const std::string& input) {
state_->handleInput(*this, input);
}
void update() {
state_->update(*this);
}
std::string getCurrentState() const {
return state_->getDescription();
}
};
// 状态处理实现
void StandingState::handleInput(Player& player, const std::string& input) {
if (input == "press jump") {
player.setState(std::make_unique<JumpingState>());
} else if (input == "press run") {
player.setState(std::make_unique<RunningState>());
}
}
void JumpingState::handleInput(Player& player, const std::string& input) {
if (input == "land") {
player.setState(std::make_unique<StandingState>());
}
}
void RunningState::handleInput(Player& player, const std::string& input) {
if (input == "stop") {
player.setState(std::make_unique<StandingState>());
} else if (input == "press jump") {
player.setState(std::make_unique<JumpingState>());
}
}
// 客户端代码
int main() {
Player player;
std::cout << "Initial state: " << player.getCurrentState() << "\n";
player.update();
player.handleInput("press run");
player.update();
player.handleInput("press jump");
player.update();
player.handleInput("land");
player.update();
return 0;
}
7. 运行结果
Initial state: Standing
Player is standing firmly
Player state changed to: Running
Player is running fast
Player state changed to: Jumping
Player is in the air
Player state changed to: Standing
Player is standing firmly
关键输出解释:
-
初始状态为Standing
-
处理"press run"输入后,状态转换为Running
-
处理"press jump"输入后,状态转换为Jumping
-
处理"land"输入后,状态恢复为Standing
-
每次状态转换后,update()方法输出当前状态特有的行为
8. 应用场景
实际项目中的应用案例
cpp标准库:
-
文件流的状态管理(good/eof/fail/bad)
-
正则表达式匹配器的状态
游戏开发:
-
角色动画状态机
-
AI行为状态管理
-
游戏流程控制(菜单、游戏、暂停等)
系统编程:
-
线程状态管理
-
网络协议状态机(如HTTP请求处理)
-
设备驱动状态控制
行业特定应用
嵌入式系统:
-
传感器状态管理
-
低功耗模式切换
-
硬件初始化流程
高性能计算:
-
计算任务状态跟踪
-
并行算法阶段管理
-
数据处理流水线控制
9. 变体
主要变体实现
基础变体:经典GoF实现(如上例)
状态表变体:
class StateMachine {
std::unordered_map<State, std::unordered_map<Event, State>> transitions;
State current;
public:
void handle(Event e) {
if (transitions[current].count(e)) {
current = transitions[current][e];
}
}
};
模板化状态机:
template<typename StateEnum>
class GenericStateMachine {
StateEnum current;
// 使用函数指针或std::function处理转换
};
变体对比表格
| 变体名称 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 经典GoF实现 | 面向对象,易扩展 | 对象创建开销 | 复杂状态逻辑 |
| 状态表 | 高效,配置灵活 | 不易调试 | 大量简单状态 |
| 模板化 | 类型安全,可复用 | 编译时确定 | 已知状态集 |
10. 状态模式 vs. 其他模式
与策略模式的区别
-
状态模式:状态转换由状态对象控制,状态知道其他状态
-
策略模式:策略通常不知道其他策略,转换由客户端控制
与命令模式的区别
-
状态模式:关注对象内部状态变化
-
命令模式:关注请求的封装和执行
选择指南
-
当行为主要依赖状态时:选择状态模式
-
当需要动态切换算法时:选择策略模式
-
当需要撤销/重做功能时:选择命令模式
11. 状态模式的优缺点
优点分析
- 消除条件分支
-
将状态相关行为局部化到状态类中
-
避免大型switch/case语句
-
示例:游戏角色行为代码更清晰
- 易于扩展新状态
-
符合开闭原则
-
添加新状态不影响现有代码
-
示例:添加"蹲下"状态只需新增类
- 状态转换显式化
-
转换逻辑集中在状态类中
-
比分散的条件语句更易维护
缺点分析
- 运行时开销
-
需要创建多个状态对象
-
频繁转换可能影响性能
-
缓解:使用状态对象池
- 增加类数量
-
每个状态需要一个类
-
可能导致类爆炸
-
缓解:将简单状态合并
12. 什么时候不要使用状态模式?
明确的反对信号
-
状态数量很少(<3个):简单枚举+switch更合适
-
状态无行为差异:仅需标记状态时,使用flag变量
-
性能关键路径:对象创建开销不可接受时
反模式警示
- 上帝状态对象
-
一个状态类知道太多其他状态
-
正确做法:状态只了解直接可达状态
- 上下文膨胀
-
上下文类包含太多与状态无关的功能
-
正确做法:保持上下文精简
- 循环依赖
-
状态和上下文相互过度依赖
-
正确做法:单向依赖(状态->上下文)
13. 总结
核心价值总结
状态模式是管理对象行为随状态变化的优雅解决方案,特别适合:
-
复杂的状态转换逻辑
-
需要频繁添加新状态的场景
-
消除条件语句的需求
最佳实践建议
-
使用智能指针管理状态对象生命周期
-
考虑状态对象的复用(对象池)
-
保持状态转换逻辑集中
-
为状态机定义清晰的接口
-
编写单元测试覆盖所有状态转换
进阶学习路径
-
研究有限状态机(FSM)理论
-
学习层次状态机和状态图表
-
探索Boost.MSM等状态机库
-
实践更复杂的游戏AI状态管理
完整项目示例(CMake)
cmake_minimum_required(VERSION 3.15)
project(StatePatternExample)
set(CMAKE_CXX_STANDARD 17)
add_executable(state_pattern_example
src/main.cpp
src/player.cpp
src/states.cpp
)
# 单元测试
enable_testing()
add_executable(state_pattern_tests
test/test_states.cpp
)
target_link_libraries(state_pattern_tests PRIVATE gtest_main)
add_test(NAME state_pattern_tests COMMAND state_pattern_tests)
单元测试示例(Google Test):
#include "gtest/gtest.h"
#include "../src/player.h"
#include "../src/states.h"
TEST(PlayerStateTest, InitialState) {
Player player;
EXPECT_EQ(player.getCurrentState(), "Standing");
}
TEST(PlayerStateTest, RunningTransition) {
Player player;
player.handleInput("press run");
EXPECT_EQ(player.getCurrentState(), "Running");
}
TEST(PlayerStateTest, JumpFromRunning) {
Player player;
player.handleInput("press run");
player.handleInput("press jump");
EXPECT_EQ(player.getCurrentState(), "Jumping");
}
通过状态模式,可以创建出清晰、可维护的状态管理系统,特别适合cpp中需要管理复杂对象行为的场景。
更多推荐
所有评论(0)