C++状态模式实战:用多态重构网络连接状态机
2026/9/8 8:00:55 网站建设 项目流程

前阵子我重构一个历史遗留网络库,打开connect()函数的时候,一口气差点没上来——里面光 if-else 就堆了几十行,后头send()又是另一套判定,再往后断线重连、鉴权超时的逻辑散落在各个角落。后来我把整套东西改成 C++ 状态模式,才总算从“改一个状态就牵一发动全身”的泥潭里爬出来。这篇博文不讲教科书套路,直接聊聊我在实际项目里的应用思路:状态模式到底解决什么问题、C++ 里怎么落地、现代写法怎么避内存坑、状态流转两种风格怎么选,以及面试最爱问的状态模式和策略模式到底怎么区分。无论你是刚学设计模式的新手,还是在准备 C++ 面试想拿高分的人,下面这些内容应该都能派上用场。

1. 状态模式解决的问题:当业务逻辑被 if-else 淹没

1.1 一段让人窒息的真实代码

先给大家还原一下我最初接手的那个网络模块长什么样:

// 伪代码示意,真实项目里比这个更乱 enum class ConnState { Disconnected, Connecting, Connected, Disconnecting }; class OldNetworkClient { public: void connect() { if (state_ == ConnState::Disconnected) { // 发起连接 state_ = ConnState::Connecting; } else if (state_ == ConnState::Connecting) { log("重复连接?"); } else if (state_ == ConnState::Connected) { log("已连接,别再连了"); } else if (state_ == ConnState::Disconnecting) { log("正在断开中,等一下"); } } void send(const std::string& data) { if (state_ == ConnState::Connected) { // 真正发送 } else if (state_ == ConnState::Connecting) { log("还没连上,不能发"); } else if (state_ == ConnState::Disconnected) { log("已断开,更不能发"); } else { log("断开中,等会再说"); } } private: ConnState state_; };

这段代码的问题不是“慢”,而是“脆弱”。每次加一个状态,比如“Reconnecting 重连中”,你就得把所有函数翻一遍,在每处分支里补一个新的 else-if。漏掉任何一个,运行时就可能出现这个状态没有被任何分支处理的情况,而编译器不会给你任何提示。状态一多,代码里全是“我现在是谁”的询问,业务逻辑反而被淹没在条件判断里。

1.2 状态模式的核心思想:把状态变成对象

状态模式的核心想法非常朴素:不要再让外部反复询问“你现在是什么状态”,而是直接把当前状态变成一个对象。当你在某个状态下调用某个操作时,执行逻辑的代码就在那个状态对象里,根本不需要条件判断。

用一句装逼但准确的话说:继承和多态取代了 if-else

还是网络连接这个例子,如果用状态模式来组织,connect()send()会变成什么样子?大致是这样:

class NetworkContext { public: void connect() { state_->connect(*this); } void send(const std::string& data) { state_->send(*this, data); } private: NetworkState* state_; };

state_是“断开状态对象”,那connect()就是发起连接然后切到“连接中”状态;state_是“连接中状态对象”,那connect()就是打日志提示重复连接;state_是“连接中状态对象”,那send()就是提示“还没连上”。每个状态把自己在该事件下的行为封装起来,新增状态就等于新增一个类,原有的类一行都不用改,这就贴合了开闭原则。

很多人第一次看状态模式会觉得“绕了一圈好像也没少写代码”。确实,状态模式的代码总量不一定比 if-else 少,但它的收益体现在后续维护上:状态行为内聚、新增状态不侵入旧代码、全部转换逻辑能被清晰梳理。

1.3 三种吃灰方案的对比:枚举、函数指针表、状态对象

有些同学会说:“我不用状态模式,给我一个枚举加 switch 照样能写。”这话对,但只对状态少的时候成立。下面我把常见的三种方案放在一起对比:

  • 枚举 + switch:写起来最直观,状态少时性价比最高;状态一多,每个 switch 都要加分支,漏一个就是运行期事故。
  • 函数指针表:能用,但 C 风格太强,代码可读性差,状态切换后指针怎么管理也容易乱。
  • 状态对象(模式方案):每个状态一个类,行为内聚,新增状态不破坏旧代码,缺点是类数量变多,需要一定的抽象能力。
维度枚举 + switch函数指针表状态对象
可读性状态多后急剧变差中等
可扩展性差,改所有分支中等好,新增一个类即可
代码量中等多,但结构清晰
适合规模2~3 个状态3~5 个状态3 个状态以上或状态经常新增

我自己用下来有个直观感受:当状态数少于 3 的时候,别上状态模式,if 就是最好的方案;一旦状态数超过 3 且行为差异明显,状态模式的收益就出来了。

2. 三个核心角色与 C++ 静态结构

2.1 Context、State、ConcreteState 的分工

状态模式涉及三类角色,理解了它们的职责边界,代码怎么写都不会跑偏:

  • Context(上下文):持有一个状态对象的引用,对外暴露业务操作接口(如connect()),内部把请求转发给当前状态对象。Context 负责保存数据,比如网络地址、socket 句柄等。
  • State(抽象状态类):定义一个接口,声明所有状态都要实现的处理函数,通常是纯虚函数。
  • ConcreteState(具体状态类):实现某个具体状态下的行为。它是整个模式中真正干活的类,数量跟状态数量一致。
class Document; // 前置声明,让State能访问Context class State { public: virtual ~State() = default; virtual void publish(Document& doc) = 0; // 每个状态处理"发布"事件 }; class Document { public: explicit Document(State* state) : state_(state) {} void publish() { state_->publish(*this); } void setState(State* state) { state_ = state; } private: State* state_; };

我讲设计模式时总会强调一点:别把 Context 做成上帝类。Context 只做两件事——保存状态和转发请求,业务核心逻辑都放状态类里。否则 Context 又会慢慢膨胀成一个大杂烩。

2.2 为什么状态对象通常设计为无状态

很多初学者会把业务数据放进状态类里,比如在ConnectedState里保存 socket 句柄。这个做法在后面会踩大坑,因为状态模式里的具体状态对象,在实际设计时经常被设计成“无状态”,也就是这个对象里不该有可变成员变量。

为什么?因为一个ConnectedState实例完全可以被多个 Context 共享。你想想,如果两个网络客户端都处于已连接状态,它们是否需要用两个不同的ConnectedState实例?不需要,它们的行为逻辑完全一样,只是里面的 socket 不同。而 socket 这种数据应该存在 Context 里。

状态对象保存的是“规则”,不是“数据”。一份规则可以被反复套用,只要数据放在各自 Context 里就行。

顺带提一句,这个思路跟享元模式(Flyweight)是一致的:把不变的、可以共享的部分抽出来复用以节省资源。状态模式的工程实现里,状态对象往往就是全程序一份静态实例,用static保证唯一。

2.3 状态切换的最小实现骨架

我们用一个更具体的例子把完整骨架串起来:假设一个文档有“草稿”、“审核中”、“已发布”三种状态,publish操作在不同状态下的行为不同。

#include <iostream> class Document; class State { public: virtual ~State() = default; virtual void publish(Document& doc) = 0; }; class ModerationState; // 前置声明,避免文件include循环 class DraftState : public State { public: void publish(Document& doc) override; }; class ModerationState : public State { public: void publish(Document& doc) override; }; class PublishedState : public State { public: void publish(Document& doc) override; }; class Document { public: explicit Document(State* state) : state_(state) {} void setState(State* state) { state_ = state; } void publish() { state_->publish(*this); } private: State* state_; }; void DraftState::publish(Document& doc) { std::cout << "草稿提交审核\n"; doc.setState(new ModerationState()); } void ModerationState::publish(Document& doc) { std::cout << "审核不通过,回到草稿\n"; doc.setState(new DraftState()); } void PublishedState::publish(Document& doc) { std::cout << "已发布,不能再操作\n"; }

这个骨架够简单了吧?但注意我在DraftState::publish里写了new ModerationState()——这个new谁来释放?这就是我在第 3 节要重点展开的 C++ 陷阱。

3. 现代 C++ 实现细节:从裸指针到智能指针

3.1 经典写法的内存隐患

如果你搜索“C++ 状态模式”,网上大批代码都是裸指针 +new满天飞。比如上一节的写法,Document里只是保存了一个裸指针,那么问题来了:

  • doc.setState(new DraftState())时,原来那个new ModerationState()对象被谁释放?
  • Document析构时要不要释放state_
  • 有没有可能在某个分支里对同一个状态重复new

正经做法要么让Document独占状态对象,析构时释放;要么使用 “状态实例共享、Context 不删除” 的策略。但很多教程讲不清楚,最终结果就是状态切一次就泄漏一次,状态越多泄漏越狠。

我用一个比喻帮大家理解:状态对象就好比公司制度,每个部门执行同一套制度,但不需要每个员工都复印一份制度文本带走。如果每次切换状态都重新复印一份,走的时候还不销毁,公司很快就会被废纸塞满。

3.2 用静态状态实例代替频繁 new

既然状态对象是无状态的、可共享的,最稳妥的做法是把具体状态类做成单例。这样 Context 持有裸指针,但指针指向静态对象,不负责删除,也不会重复创建。

class NetworkState { public: virtual ~NetworkState() = default; virtual void handleConnect(NetworkContext& ctx) {} virtual void handleSend(NetworkContext& ctx) {} }; class DisconnectedState : public NetworkState { public: static DisconnectedState* instance() { static DisconnectedState inst; return &inst; } void handleConnect(NetworkContext& ctx) override; }; class NetworkContext { public: NetworkContext() : state_(DisconnectedState::instance()) {} void setState(NetworkState* state) { state_ = state; } void connect() { state_->handleConnect(*this); } private: NetworkState* state_; };

这里static DisconnectedState inst;是函数级静态变量,初始化线程安全且只在首次调用时构造,用起来很省心。裸指针没问题,因为它的生命周期是整个程序的,Context 根本不需要负责释放。

注意:如果你把静态状态实例和“带成员数据的状态对象”混在一起用,就会出现一个线程改坏了状态对象、另一个线程立刻读到的并发事故。这就是我反复强调状态对象必须无状态的原因。

如果你一定要用std::unique_ptr管理状态对象,也可以,但要注意所有权转移:

class Context { public: void setState(std::unique_ptr<State> state) { state_ = std::move(state); // 旧的自动析构 } private: std::unique_ptr<State> state_; };

这种写法适合状态对象内部确实需要持有上下文相关数据、无法共享的场景,缺点是状态切换时会产生频繁的构造析构。项目里我一般优先推荐静态实例方案,代码更轻,也符合状态对象无状态的最佳实践。

3.3 另一种思路:C++17 的 std::variant 状态机

如果你用的是 C++17 以上版本,还有一个越来越流行的替代方案:用std::variant表示状态,用std::visit分发事件。它的思路是把所有可能的状态类型放进一个联合体,用类型本身代替枚举,再配合std::visit写处理逻辑。

#include <variant> struct Disconnected {}; struct Connecting {}; struct Connected {}; using State = std::variant<Disconnected, Connecting, Connected>; struct Context { void connect() { // 用 std::visit 分发给当前状态处理 std::visit([](auto&& state) { using T = std::decay_t<decltype(state)>; if constexpr (std::is_same_v<T, Disconnected>) { // 发起连接 } }, state_); } State state_; };

我要说清楚:std::variant写法解决的是“状态对象生命周期管理”的问题,但也引入了新的取舍。它的优点是性能好、局部性强、所有状态一目了然;缺点是加一种新状态时,所有std::visit的地方都得确认有没有处理新类型,否则可能出现未知的运行时行为,这跟状态模式“开闭原则”的初衷是相悖的。

我自己的判断标准是:状态集合固定、几乎不会新增时,std::variant很方便;状态经常变动、团队维护人多的老项目,还是传统状态模式更稳。

4. 状态流转的两条路线:状态自治与外部控制器

4.1 状态自治流转:灵活但依赖方向危险

前面文档例子里的写法就属于“状态自治”:DraftState::publish里自己决定下一个状态是ModerationState,切换逻辑内聚在状态内部。

好处是直观,写完DraftState你就知道它能走到哪儿去,不会再出现“状态 A 的某条出口没人接”的情况。坏处呢?转换关系被打散在各个状态类里,想一眼看懂整个状态图几乎不可能。状态一多,你得把所有状态类的源码都翻一遍才能拼出完整转换表,而且每个状态类要 include 下一个状态类,类之间的依赖关系会变得很重。

4.2 集中式流转:清晰但样板代码多

另一种风格是“中央控制器”,做法是:状态对象处理完事件后,返回一个结果或事件码,由 Context 或一个专门的转换表来查下一个状态。

enum class Event { Connect, Connected, Disconnect, Disconnected }; class NetworkContext { static std::unordered_map<StateType, std::unordered_map<Event, StateType>> transition_table_; public: void dispatch(Event ev) { auto next = transition_table_[current_type_][ev]; if (next != StateType::Invalid) { setState(next); } } };

转换表集中放在一个地方,状态图一目了然,新增一条转换路径只需改表。代价是每个状态的处理逻辑都变成了“返回一个事件”,方法体内不再直接写setState,状态对象内部的行为内聚性会弱一些。

4.3 选择原则与工程经验

对比维度状态自治集中式转换表
转换逻辑位置在各状态类内部集中在一张表/一个函数
可读性状态多时分散状态图一眼可见
依赖方向状态依赖其他状态类状态只依赖事件,依赖更轻
SetState 调用点多个一个
适合场景状态少、转换关系内聚状态多、事件多、需要审核/审计

我的经验是:刚开始接触状态模式,先用状态自治,因为直观、易于理解,也能快速把逻辑跑通;等状态数膨胀到五六种以上、转换关系变得复杂、团队经常需要讨论“这个事件下该去哪儿”时,果断迁到集中式转换表。

另外,无论选哪种风格,建议都先把状态图画出来,哪怕只是手绘在纸上。我试过在一个模块里一口气写了十几个状态类,结果到头来发现缺少一条切换路径,运行到某一步就死锁了——状态图能帮你避免这类低级错误。

5. 状态模式与策略模式的区别:面试高频考点深度拆解

5.1 结构几乎一样,意图完全不同

这是 C++ 面试八股文里一个极其经典的对比题,因为两个模式的类图几乎一模一样,都是“Context + 抽象基类 + 多个实现类”,导致很多人一进面试就懵。

说到底,它们的意图有一个根本分界:

  • 策略模式(Strategy):解决“同一件事怎么做”的问题。比如排序算法,你可以用快排、归并、冒泡,外部选择一种策略后,行为就固定了,不会因为执行过程自动切换策略。
  • 状态模式(State):解决“不同状态下做什么”的问题。状态对象的行为不仅不同,而且会随着时间推移、事件发生而自动切换,自己是会“变”的。

5.2 一句话判断法

我把这个区分方法精简成一句话:策略模式中的策略是“选完了就不变”,状态模式中的状态是“做完一件事自动跳转”

举个例子。一个加密模块,支持 AES 和 SM4,你在初始化时选定一种加密算法,之后加密就固定用这一个——这是策略模式。一个 TCP 连接,从Disconnected发起连接变Connecting,连接成功后变Connected,断开后又回去——这是状态模式。

判断代码属于哪一种,就看一件事:是否存在状态自动流转。存在流转就是状态模式,不存在就是策略模式。

5.3 与有限状态机的边界

还有一个相关概念叫有限状态机(FSM)。我用一个精确的说法:状态模式是 FSM 的一种实现载体,但 FSM 远不止状态模式

FSM 还包括事件队列、进入动作(entry action)、退出动作(exit action)、守卫条件(guard)等技术细节。状态模式通常只解决了“状态行为如何组织”的问题,并没有明确规定“进入新状态时是否要执行一个 hook 函数”“状态转移前如何校验守卫条件”等。

如果你要实现的系统对这些能力有硬性要求,比如状态进入时必须初始化一些东西、离开时必须清理,那么要么在状态类里自己实现onEnter/onExit接口,要么直接引入一个成熟的 FSM 库。自己在状态模式之上加这两个 hook 是个很自然的扩展方向,很多项目的状态基类里都会定义virtual void onEnter(Context&)virtual void onExit(Context&),默认空实现。

6. 完整实战:网络连接状态机(含多线程保护)

6.1 状态定义与转换表

整篇讲到现在,我拿一个相对完整的网络连接例子来把前面的知识点全部串起来。场景如下:

  • 状态有 4 个:DisconnectedConnectingConnectedDisconnecting
  • 事件有 5 个:connect()onConnected()onConnectFailed()send()disconnect()
当前状态connectonConnectedonConnectFaileddisconnectsend
Disconnected→ Connecting忽略忽略忽略提示未连接
Connecting提示重复连接→ Connected→ Disconnected忽略提示连接中
Connected提示已连接忽略忽略→ Disconnecting真正发送
Disconnecting提示断开中忽略忽略忽略提示断开中

这个转换表看着很清楚,实现时我用“状态自治”风格,让每个状态完成自己的职责后切换到下一个状态。

6.2 代码实现

这里我给出一个可以直接编译运行的完整版本。状态实例采用静态单例,避免裸指针管理问题;Context 里加了一个std::mutex保护状态切换,避免多线程环境下状态检查与切换之间出现竞态。

#include <iostream> #include <string> #include <mutex> class NetworkContext; class NetworkState { public: virtual ~NetworkState() = default; virtual void connect(NetworkContext& ctx) { std::cout << "[state] connect ignored\n"; } virtual void onConnected(NetworkContext& ctx) { std::cout << "[state] onConnected ignored\n"; } virtual void onConnectFailed(NetworkContext& ctx) { std::cout << "[state] onConnectFailed ignored\n"; } virtual void disconnect(NetworkContext& ctx) { std::cout << "[state] disconnect ignored\n"; } virtual void send(NetworkContext& ctx, const std::string&) { std::cout << "[state] send ignored\n"; } }; class DisconnectedState final : public NetworkState { public: static DisconnectedState* instance() { static DisconnectedState inst; return &inst; } void connect(NetworkContext& ctx) override; void send(NetworkContext&, const std::string&) override; }; class ConnectingState final : public NetworkState { public: static ConnectingState* instance() { static ConnectingState inst; return &inst; } void connect(NetworkContext& ctx) override; void onConnected(NetworkContext& ctx) override; void onConnectFailed(NetworkContext& ctx) override; void send(NetworkContext&, const std::string&) override; }; class ConnectedState final : public NetworkState { public: static ConnectedState* instance() { static ConnectedState inst; return &inst; } void connect(NetworkContext& ctx) override; void disconnect(NetworkContext& ctx) override; void send(NetworkContext& ctx, const std::string& data) override; }; class DisconnectingState final : public NetworkState { public: static DisconnectingState* instance() { static DisconnectingState inst; return &inst; } void disconnect(NetworkContext& ctx) override; void send(NetworkContext&, const std::string&) override; }; class NetworkContext { public: NetworkContext() : state_(DisconnectedState::instance()) {} void connect() { std::lock_guard<std::mutex> lk(mtx_); state_->connect(*this); } void onConnected() { std::lock_guard<std::mutex> lk(mtx_); state_->onConnected(*this); } void onConnectFailed() { std::lock_guard<std::mutex> lk(mtx_); state_->onConnectFailed(*this); } void disconnect() { std::lock_guard<std::mutex> lk(mtx_); state_->disconnect(*this); } void send(const std::string& data) { std::lock_guard<std::mutex> lk(mtx_); state_->send(*this, data); } void setState(NetworkState* state) { std::cout << "[transition] -> " << stateName(state) << "\n"; state_ = state; } static const char* stateName(NetworkState* state); private: NetworkState* state_; std::mutex mtx_; }; // ---- 状态行为实现 ---- void DisconnectedState::connect(NetworkContext& ctx) { std::cout << "[Disconnected] 开始连接...\n"; ctx.setState(ConnectingState::instance()); } void DisconnectedState::send(NetworkContext&, const std::string&) { std::cout << "[Disconnected] 未连接,不能发送\n"; } void ConnectingState::connect(NetworkContext&) { std::cout << "[Connecting] 正在连接,请勿重复操作\n"; } void ConnectingState::onConnected(NetworkContext& ctx) { std::cout << "[Connecting] 连接成功\n"; ctx.setState(ConnectedState::instance()); } void ConnectingState::onConnectFailed(NetworkContext& ctx) { std::cout << "[Connecting] 连接失败\n"; ctx.setState(DisconnectedState::instance()); } void ConnectingState::send(NetworkContext&, const std::string&) { std::cout << "[Connecting] 连接中,不能发送\n"; } void ConnectedState::connect(NetworkContext&) { std::cout << "[Connected] 已连接,别再连了\n"; } void ConnectedState::disconnect(NetworkContext& ctx) { std::cout << "[Connected] 主动断开中...\n"; ctx.setState(DisconnectingState::instance()); } void ConnectedState::send(NetworkContext&, const std::string& data) { std::cout << "[Connected] 发送数据: " << data << "\n"; } void DisconnectingState::disconnect(NetworkContext&) { std::cout << "[Disconnecting] 已经在断开了\n"; } void DisconnectingState::send(NetworkContext&, const std::string&) { std::cout << "[Disconnecting] 正在断开,不能发送\n"; } const char* NetworkContext::stateName(NetworkState* state) { if (state == DisconnectedState::instance()) return "Disconnected"; if (state == ConnectingState::instance()) return "Connecting"; if (state == ConnectedState::instance()) return "Connected"; return "Disconnecting"; } int main() { NetworkContext client; client.connect(); // 连接 client.send("hello"); // 连接中,不能发送 client.onConnected(); // 连接成功 client.send("hello"); // 发送成功 client.connect(); // 已连接 client.disconnect(); // 断开中 client.send("hello"); // 正在断开 client.disconnect(); // 已经在断开了 client.setDisconnectedForTest(nullptr); // 伪代码,仅示意不会这样写 return 0; }

严格说上面的main里没有模拟Disconnecting -> Disconnected的完成回调,实际项目中会在网络库回调里调用onDisconnected(),我再补一个默认行为就能闭环。这里主要是展示“每个状态都明确知道自己能处理哪些事件、不能处理哪些事件”,代码跑一遍输出很直观。

6.3 测试与验证

上面代码的运行输出大致如下:

[Disconnected] 开始连接... [transition] -> Connecting [Connecting] 连接中,不能发送 [Connecting] 连接成功 [transition] -> Connected [Connected] 发送数据: hello [Connected] 已连接,别再连了 [Connected] 主动断开中... [transition] -> Disconnecting [Disconnecting] 正在断开,不能发送 [Disconnecting] 已经在断开了

从输出可以看到,非法操作被“静默忽略”或提示错误,而不是每个入口都堆 if-else。这就是状态模式最直观的好处:每个状态只关注自己需要处理的事件,非法事件统一有兜底。

7. 实战中的坑:状态爆炸、滥用模式与调试技巧

7.1 过度设计警告:什么场景不值得用状态模式

我在前面强调过“状态少于 3 个别用状态模式”,这里再展开说一点。状态模式也有自己的成本:类数量成倍增加、代码跳转变多、阅读时需要频繁切换文件。如果一个模块只有两个状态,比如“开关”,用状态模式反而会让维护者骂娘。

判断要不要上状态模式,我一般看三条:

  1. 状态数量:至少 3 个,否则 if-else 最直白。
  2. 行为差异:不同状态对同一事件的处理差异明显,而不是只改一个布尔值。
  3. 变化频率:状态会持续增加,或者转换关系经常调整。

如果三条一条都不满足,用状态模式就是过度设计。

7.2 状态对象生命周期与并发切换

状态模式在多线程环境里有一个很隐蔽的坑:判断状态和执行操作不是一个原子操作。比如在send()里先查了state_ == Connected,正准备发送时,另一个线程把状态切成了Disconnecting,这时你的数据实际是在断开过程中发出的。

解决方式我在实战代码里已经演示了:在 Context 的对外接口上统一加锁,把“读取状态 + 调用状态行为 + 状态切换”都放进临界区。注意锁的粒度要覆盖整个操作,而不是只在setState里加锁。

另外,如果需要跨线程发送“状态变更事件”,比如异步回调里触发onConnected(),请务必保证回调里拿到的是 Context 的引用,状态对象本身不要持有可变数据,否则两个线程同时访问状态单例,问题就变得非常难查。

7.3 调试技巧:状态日志与现场重建

我调状态机最依赖的工具就是状态切换日志。在NetworkContext::setState里加一行[transition] -> xxx这种输出,看着不起眼,但排查问题时特别有用。

举个例子,之前遇到一个异常,表现是“偶尔重连失败,但失败日志没有规律”。我在代码里补了状态切换日志后,立刻发现有一条onConnectFailed之后状态没有回到Disconnected,而是保持了Connecting,后续所有重连请求都被“正在连接”拦截了。如果没打状态切换日志,这种问题靠肉眼根本不可能定位。

建议大家在设计状态基类时把以下日志统一加进去:

  • 进入新状态时,打印状态名称和触发事件。
  • 离开旧状态时,打印去向。
  • 被忽略的非法事件,也要打一条 warning。

上线后把日志收集起来,就能像回放录像一样重建整个状态轨迹,排查效率翻倍。

最后再说一个我做这类重构的习惯:如果时间允许,我通常先在旧代码上用枚举加 switch 把要实现的逻辑完整跑通,画一版状态图,确认逻辑没有遗漏,然后再迁到状态模式。这样做不是为了省事,而是避免在重构的过程中把“逻辑理解”和“模式落地”两个难题混在一起。状态模式本身不复杂,复杂的是业务里那些容易被忽略的边界情况——先把边界摸清楚,再用模式去组织它们,你会少踩很多坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询