1. 引言
在面向对象编程中,状态模式(State Pattern)是一种行为设计模式,它允许对象在内部状态改变时改变其行为。这种模式可以有效地解决状态爆炸的问题,使代码更加简洁和易于维护。 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。
2.介绍
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态类抽象出来。
关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。
应用实例: 1、打篮球的时候运动员可以有正常状态、不正常状态和超常状态。 2、曾侯乙编钟中,'钟是抽象接口','钟A'等是具体状态,'曾侯乙编钟'是具体环境(Context)。3.智能手机的按键和开关会根据设备当前状态完成不同行为。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
3. 状态模式的基本概念
状态模式的核心思想是将对象的行为根据其状态的不同而进行封装。通过定义一系列状态类,每个状态类实现特定的行为,当对象状态发生变化时,它会切换到不同的状态类,从而改变自身的行为。
3.1 模式结构
状态模式主要包含以下几个部分:
- Context(上下文):维护一个State实例,这个实例定义了当前的状态。
- State(状态):定义一个接口,以封装与Context的一个特定状态相关的行为。
- ConcreteState(具体状态):实现State接口,并且封装了与Context的一个具体状态相关的行为。
4. 状态模式的C++实现
下面,我们通过一个简单的例子来演示状态模式的实现。假设我们要实现一个简单的糖果机(CandyMachine),它有三种状态:没有硬币(NoCoinState)、有硬币(HasCoinState)和售出糖果(SoldState)。
4.1 定义状态接口
首先,我们定义一个状态接口State
,包含糖果机的常见操作:
#include <iostream>
#include <memory>
class CandyMachine;
class State {
public:
virtual ~State() = default;
virtual void insertCoin(CandyMachine& machine) = 0;
virtual void ejectCoin(CandyMachine& machine) = 0;
virtual void turnCrank(CandyMachine& machine) = 0;
virtual void dispense(CandyMachine& machine) = 0;
};
4.2 定义具体状态
接下来,我们定义具体状态类:
class NoCoinState : public State {
public:
void insertCoin(CandyMachine& machine) override;
void ejectCoin(CandyMachine& machine) override;
void turnCrank(CandyMachine& machine) override;
void dispense(CandyMachine& machine) override {
std::cout << "You need to insert a coin first.\n";
}
};
class HasCoinState : public State {
public:
void insertCoin(CandyMachine& machine) override {
std::cout << "You can't insert another coin.\n";
}
void ejectCoin(CandyMachine& machine) override;
void turnCrank(CandyMachine& machine) override;
void dispense(CandyMachine& machine) override {
std::cout << "Turn the crank to get candy.\n";
}
};
class SoldState : public State {
public:
void insertCoin(CandyMachine& machine) override {
std::cout << "Please wait, we're already giving you a candy.\n";
}
void ejectCoin(CandyMachine& machine) override {
std::cout << "Sorry, you already turned the crank.\n";
}
void turnCrank(CandyMachine& machine) override {
std::cout << "Turning twice doesn't get you another candy!\n";
}
void dispense(CandyMachine& machine) override;
};
4.3 定义上下文类
然后,我们定义上下文类CandyMachine
,它维护当前状态并提供改变状态的方法:
class CandyMachine {
public:
CandyMachine() : state(std::make_shared<NoCoinState>()) {}
void setState(std::shared_ptr<State> newState) {
state = newState;
}
void insertCoin() {
state->insertCoin(*this);
}
void ejectCoin() {
state->ejectCoin(*this);
}
void turnCrank() {
state->turnCrank(*this);
state->dispense(*this);
}
private:
std::shared_ptr<State> state;
// 声明具体状态类为友元类以便它们可以访问setState方法
friend class NoCoinState;
friend class HasCoinState;
friend class SoldState;
// 定义各具体状态实例
std::shared_ptr<State> noCoinState = std::make_shared<NoCoinState>();
std::shared_ptr<State> hasCoinState = std::make_shared<HasCoinState>();
std::shared_ptr<State> soldState = std::make_shared<SoldState>();
};
4.4 实现具体状态方法
最后,实现各具体状态的方法,其中需要改变糖果机的状态:
void NoCoinState::insertCoin(CandyMachine& machine) {
std::cout << "You inserted a coin.\n";
machine.setState(machine.hasCoinState);
}
void NoCoinState::ejectCoin(CandyMachine& machine) {
std::cout << "You haven't inserted a coin.\n";
}
void NoCoinState::turnCrank(CandyMachine& machine) {
std::cout << "You turned, but there's no coin.\n";
}
void HasCoinState::ejectCoin(CandyMachine& machine) {
std::cout << "Coin returned.\n";
machine.setState(machine.noCoinState);
}
void HasCoinState::turnCrank(CandyMachine& machine) {
std::cout << "You turned...\n";
machine.setState(machine.soldState);
}
void SoldState::dispense(CandyMachine& machine) {
std::cout << "A candy comes rolling out the slot.\n";
machine.setState(machine.noCoinState);
}
5.5. 测试状态模式
现在我们可以测试我们的糖果机状态模式实现:
int main() {
CandyMachine machine;
machine.insertCoin();
machine.turnCrank();
machine.insertCoin();
machine.ejectCoin();
machine.turnCrank();
machine.insertCoin();
machine.turnCrank();
machine.insertCoin();
machine.turnCrank();
machine.ejectCoin();
return 0;
}
考虑我们前面的糖果机例子,如果我们不使用状态模式,所有的状态转换逻辑可能会集中在CandyMachine
类中,代码可能如下:
class CandyMachine {
public:
void insertCoin() {
if (state == "NoCoin") {
state = "HasCoin";
std::cout << "You inserted a coin.\n";
} else if (state == "HasCoin") {
std::cout << "You can't insert another coin.\n";
} else if (state == "Sold") {
std::cout << "Please wait, we're already giving you a candy.\n";
}
}
void ejectCoin() {
if (state == "NoCoin") {
std::cout << "You haven't inserted a coin.\n";
} else if (state == "HasCoin") {
state = "NoCoin";
std::cout << "Coin returned.\n";
} else if (state == "Sold") {
std::cout << "Sorry, you already turned the crank.\n";
}
}
void turnCrank() {
if (state == "NoCoin") {
std::cout << "You turned, but there's no coin.\n";
} else if (state == "HasCoin") {
state = "Sold";
std::cout << "You turned...\n";
dispense();
} else if (state == "Sold") {
std::cout << "Turning twice doesn't get you another candy!\n";
}
}
void dispense() {
if (state == "Sold") {
state = "NoCoin";
std::cout << "A candy comes rolling out the slot.\n";
} else if (state == "NoCoin" || state == "HasCoin") {
std::cout << "No candy dispensed.\n";
}
}
private:
std::string state = "NoCoin";
};
由此可得出:
5.状态模式的优缺点
优点
-
简化状态转换逻辑:
- 状态模式通过将状态的行为封装到独立的状态类中,避免了在上下文类中使用大量的条件语句或分支逻辑来处理状态转换。代码变得更加清晰和易读。
-
增强可维护性和可扩展性:
- 新增或修改状态只需创建新的状态类或修改现有状态类,不需要修改上下文类的代码。这使得系统更容易扩展和维护。
-
状态独立且自包含:
- 每个状态类只关注自己的行为和状态转换逻辑,职责单一,符合单一职责原则(SRP),使代码更具模块化。
-
减少对象之间的耦合:
- 上下文类与具体状态类通过状态接口解耦,改变一个状态不会影响其他状态类或上下文类,有助于降低系统的耦合度。
-
提高代码复用性:
- 通过将状态逻辑分散到多个状态类中,可以在不同的上下文中复用这些状态类,提高代码的复用性。
缺点
-
类的数量增加:
- 使用状态模式时,每个状态都需要定义一个类。如果状态较多,会导致类的数量显著增加,管理和维护变得复杂。
-
逻辑分散:
- 状态转换逻辑分散在各个状态类中,可能导致难以理解整个状态转换过程。调试时需要查看多个类,增加了理解和调试的难度。
-
状态切换时的开销:
- 每次状态切换都需要创建新的状态对象或改变状态引用,这可能会引入一些额外的性能开销,特别是在高频率状态切换的情况下。
-
需要了解设计模式:
- 开发人员需要熟悉状态模式的概念和实现,才能正确地设计和实现该模式。如果团队成员对该模式不熟悉,可能会导致设计和实现上的困难。
6. 总结
通过上面的例子,我们可以看到状态模式的强大之处。它使得状态转换逻辑更加清晰,避免了大量的条件语句,提高了代码的可读性和可维护性。在实际开发中,状态模式可以应用于各种需要状态转换的场景,如工作流引擎、游戏角色状态管理等。