一.意图
状态是一种行为设计模式,允许对象在其内部状态变化时改变其行为。看起来对象似乎改变了它的类。
允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。------《设计模式》GoF
二.问题
状态模式与有限状态机的概念密切相关

其主要思想是,在任何给定时刻,程序可以处于有限 的状态 。在任何唯一状态内,程序的行为不同,程序可以瞬间从一个状态切换到另一个状态。然而,根据当前状态,程序可能会或不会切换到某些其他状态。这些切换规则,称为转移,也是有限且预先确定的。
你也可以将这种方法应用于对象。想象我们有一个类。文档可以处于三种状态之一。文档的方法在每种状态下工作方式略有不同:
-
在Draft中,它将文档移至审核状态。
-
在 Moderation中,它会将文档公开,但前提是当前用户是管理员。
-
在Published中,它完全没有作用。

状态机通常通过大量条件语句(或)实现,这些语句根据对象当前状态选择合适的行为。通常,这个"状态"只是对象场的一组值。即使你以前从未听说过有限状态机,你很可能至少实现过一次状态。以下代码结构有印象吗?
class Document is
field state: string
// ...
method publish() is
switch (state)
"draft":
state = "moderation"
break
"moderation":
if (currentUser.role == "admin")
state = "published"
break
"published":
// Do nothing.
break
// ...
基于条件句的状态机最大的弱点是在我们开始向类中添加越来越多的状态和依赖状态的行为时显现出来的。大多数方法会包含庞大的条件句,根据当前状态选择方法的正确行为。这类代码非常难以维护,因为任何对转换逻辑的更改都可能需要在每个方法中更改状态条件句。
随着项目的发展,问题往往会变得更大。在设计阶段预测所有可能的状态和转变相当困难。因此,用有限条件句构建的精益状态机随着时间推移可能会变得臃肿不堪。
三.解决方案
状态模式建议你为对象的所有可能状态创建新类,并将所有状态特定的行为提取到这些类中。
原始对象(称为上下文)不会单独实现所有行为,而是存储一个代表当前状态的状态对象引用,并将所有与状态相关的工作委托给该对象。

要将上下文转移到另一个状态,将激活状态对象替换为代表该新状态的另一个对象。这只有在所有状态类都遵循相同接口且上下文本身通过该接口与这些对象工作时才可能。
这种结构看起来可能类似于策略模式,但有一个关键区别。在状态模式中,特定状态可能彼此知晓,并发起从一个状态到另一个状态的转移,而策略几乎从不了解彼此。
四.现实世界的类比
你手机中的按钮和开关会根据设备当前状态表现不同:
-
手机解锁后,按下按钮会执行各种功能。
-
当手机被锁定时,按下任意按钮都会进入解锁界面。
-
当手机电量低时,按下任意按钮会显示充电屏幕。
五.结构

六.适合应用场景
-
当一个对象根据当前状态表现不同,状态数量庞大且状态特定代码频繁变化时,可以使用状态模式。
该模式建议你将所有状态特定的代码提取成一组不同的类。因此,你可以独立添加新状态或更改现有状态,从而降低维护成本。
-
当你有一个类被大量条件句污染时,可以使用这种模式,这些条件句会根据该类字段当前的值改变其行为。
State 模式允许你将这些条件句的分支提取成对应状态类的方法。在此过程中,你还可以从主类中清除与状态特定代码相关的临时字段和辅助方法。
-
当你在条件型状态机的类似状态和转换中有大量重复代码时,使用状态。
状态模式允许你组合状态类的层级结构,并通过将共同代码提取成抽象基类来减少重复。
七.实现方式
-
决定哪个类作为上下文。它可以是已有且已有状态依赖代码的类;或者如果状态特定代码分布在多个类中,则是一个新的类。
-
声明状态接口。虽然它可能镜像上下文中所有声明的方法,但只针对可能包含状态特定行为的方法。
-
对于每个实际状态,创建一个源自状态接口的类。然后检查上下文的方法,把所有与该状态相关的代码提取到你新创建的类中。
在将代码迁移到状态类时,你可能会发现它依赖于上下文中的私有成员。有几个变通方法:
-
将这些字段或方法公开。
-
把你提取的行为转化为上下文中的公共方法,然后从状态类调用它。这种方法虽然不方便,但很快,而且你以后总能修正。
-
将状态类嵌套到上下文类中,但前提是你的编程语言支持嵌套类。
-
-
在上下文类中,添加状态接口类型的引用字段和一个公共设置器,允许覆盖该字段的值。
-
再次检查上下文的方法,将空状态条件句替换为对状态对象对应方法的调用。
-
要切换上下文状态,创建一个状态类实例并将其传递给上下文。你可以在上下文内部完成,也可以在不同状态中,或者客户端中完成。无论在哪里这样做,类都会依赖于它实例化的具体状态类。
八.优缺点
-
优点:
-
单一责任原则。将与特定状态相关的代码组织为独立类别。
-
开放/封闭原则。引入新状态时不改变现有的状态类或上下文。
-
通过消除庞大的状态机条件句来简化上下文代码。
-
-
缺点
- 如果状态机只有少量状态或很少变化,应用模式可能显得过于大材小用。
九.与其他模式的关系
-
桥梁、国家、战略(以及某种程度上的适配器)结构非常相似。事实上,所有这些模式都基于构图,即将工作委托给其他对象。不过,它们各自解决的问题不同。模式不仅仅是用来构建代码的具体配方。它还能向其他开发者传达该模式所解决的问题。
-
状态可以被视为策略的延伸。这两种模式都基于组合:它们通过将部分工作委托给辅助对象来改变上下文的行为。策略使 这些物体完全独立且彼此无意识。然而,状态并不限制具体状态之间的依赖关系,允许它们随意改变上下文的状态。
十.示例代码
#include <iostream>
#include <string>
// 前置声明:环境类(售货机),因为具体状态类需要操作环境类切换状态
class VendingMachine;
// 1. 抽象状态类:定义售货机状态的统一接口
class VendingMachineState {
public:
// 纯虚函数:对应售货机的可执行行为
virtual void insertCoin() = 0; // 投币
virtual void ejectCoin() = 0; // 退币
virtual void dispenseGoods() = 0; // 选货并出货
// 虚析构函数:避免多态场景下子类内存泄漏
virtual ~VendingMachineState() = default;
// 给具体状态类提供环境类引用(方便切换状态)
void setContext(VendingMachine* context) {
this->context = context;
}
protected:
VendingMachine* context = nullptr; // 环境类指针,保护权限:子类可访问,外部不可访问
};
// 2. 环境类:自动售货机(维护当前状态,提供客户端操作接口)
class VendingMachine {
private:
VendingMachineState* currentState; // 当前状态
VendingMachineState* noCoinState; // 空闲状态(无币)
VendingMachineState* hasCoinState; // 已投币状态
VendingMachineState* soldOutState; // 售罄状态
int goodsCount; // 商品库存(控制是否进入售罄状态)
public:
// 构造函数:初始化所有状态,默认进入空闲状态(有库存)或售罄状态(无库存)
VendingMachine(int initialGoodsCount) : goodsCount(initialGoodsCount) {
// 创建所有具体状态对象
noCoinState = new VendingMachineState();
hasCoinState = new HasCoinState();
soldOutState = new SoldOutState();
// 给所有状态设置环境类引用(方便状态切换)
noCoinState->setContext(this);
hasCoinState->setContext(this);
soldOutState->setContext(this);
// 初始化当前状态
currentState = (goodsCount > 0) ? noCoinState : soldOutState;
}
// 析构函数:释放所有状态对象内存
~VendingMachine() {
delete noCoinState;
delete hasCoinState;
delete soldOutState;
}
// 客户端操作接口:转发给当前状态处理
void insertCoin() {
currentState->insertCoin();
}
void ejectCoin() {
currentState->ejectCoin();
}
void dispenseGoods() {
currentState->dispenseGoods();
}
// 状态切换接口:由具体状态类调用,修改当前状态
void setState(VendingMachineState* newState) {
currentState = newState;
std::cout << "[售货机] 状态已切换" << std::endl;
}
// 库存操作接口:出货时减少库存
void reduceGoodsCount() {
if (goodsCount > 0) {
goodsCount--;
std::cout << "[售货机] 商品库存剩余:" << goodsCount << std::endl;
}
}
// 获取各状态对象(供具体状态类切换时使用)
VendingMachineState* getNoCoinState() const { return noCoinState; }
VendingMachineState* getHasCoinState() const { return hasCoinState; }
VendingMachineState* getSoldOutState() const { return soldOutState; }
int getGoodsCount() const { return goodsCount; }
};
// 3. 具体状态类1:空闲状态(无币,有库存)
class NoCoinState : public VendingMachineState {
public:
void insertCoin() override {
std::cout << "[空闲状态] 投币成功,已进入投币状态" << std::endl;
// 切换到已投币状态
context->setState(context->getHasCoinState());
}
void ejectCoin() override {
std::cout << "[空闲状态] 错误:尚未投币,无法退币" << std::endl;
// 空闲状态下退币无意义,不切换状态
}
void dispenseGoods() override {
std::cout << "[空闲状态] 错误:尚未投币,无法出货" << std::endl;
// 空闲状态下出货无意义,不切换状态
}
};
// 4. 具体状态类2:已投币状态
class HasCoinState : public VendingMachineState {
public:
void insertCoin() override {
std::cout << "[已投币状态] 错误:已投币,无需重复投币" << std::endl;
// 已投币状态下重复投币无意义,不切换状态
}
void ejectCoin() override {
std::cout << "[已投币状态] 退币成功,返回空闲状态" << std::endl;
// 切换回空闲状态
context->setState(context->getNoCoinState());
}
void dispenseGoods() override {
std::cout << "[已投币状态] 选货成功,正在出货..." << std::endl;
// 出货并减少库存
context->reduceGoodsCount();
// 切换状态:库存为0则进入售罄状态,否则返回空闲状态
if (context->getGoodsCount() <= 0) {
context->setState(context->getSoldOutState());
} else {
context->setState(context->getNoCoinState());
}
}
};
// 5. 具体状态类3:售罄状态
class SoldOutState : public VendingMachineState {
public:
void insertCoin() override {
std::cout << "[售罄状态] 错误:商品已售罄,无法投币" << std::endl;
}
void ejectCoin() override {
std::cout << "[售罄状态] 错误:商品已售罄,无币可退" << std::endl;
}
void dispenseGoods() override {
std::cout << "[售罄状态] 错误:商品已售罄,无法出货" << std::endl;
}
};
// 客户端测试代码
int main() {
// 创建一台初始库存为2的自动售货机
VendingMachine vendingMachine(2);
std::cout << "===== 第一次操作 =====" << std::endl;
vendingMachine.insertCoin(); // 投币
vendingMachine.dispenseGoods(); // 出货
std::cout << "\n===== 第二次操作 =====" << std::endl;
vendingMachine.insertCoin(); // 投币
vendingMachine.dispenseGoods(); // 出货(库存耗尽,进入售罄状态)
std::cout << "\n===== 第三次操作(售罄后) =====" << std::endl;
vendingMachine.insertCoin(); // 尝试投币(失败)
vendingMachine.dispenseGoods(); // 尝试出货(失败)
return 0;
}