23. 状态模式
一、状态模式
状态模式(State Pattern)是设计模式中的一种,属于行为模式。它允许一个对象在其内部状态发生改变时改变它的行为,使其看起来像是修改了其类。状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。通过把状态的判断逻辑转移到表示不同状态的一系列类中,状态模式可以简化复杂的判断逻辑。状态模式中类的行为是由状态决定的,在不同的状态下有不同的行为。其意图是让一个对象在其内部状态改变时,行为也随之改变。状态模式的核心是状态与行为绑定,不同的状态对应不同的行为。
状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化。对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致性地处理。
状态模式的常用角色如下:
- 环境类(Context):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类 State 的实例,这个实例定义当前状态,在具体实现时,它是一个 State 子类的对象。
- 抽象状态类(State):它用于定义一个接口以封装与环境类的一个特定状态相关的行为。在抽象状态类中声明各种不同状态对应的方法,而在其子类中实现这些方法。由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
- 具体状态类(ConcreteState):它是抽象状态类的子类,每个子类实现一个与环境类的一个状态相关的行为。每个具体状态类对应环境类的一个具体状态,不同的具体状态类其行为有所不同。
在状态模式中引入了抽象状态类和具体状态类,它们是状态模式的核心。在状态模式中,将对象在不同状态下的行为封装到不同的状态类中。为了让系统具有更好的灵活性和可扩展性,同时对各状态下的共有行为进行封装,需要对状态进行抽象。状态模式是一种用于处理对象状态转换和行为变化的设计模式,它通过将状态与行为绑定,简化了复杂的判断逻辑,并提高了代码的灵活性和可维护性。
二、C++实现状态模式
在抽象状态类的子类即具体状态类中实现了在抽象状态类中声明的业务方法,不同的具体状态类可以提供完全不同的方法实现。在实际使用时,一个状态类中可能包含多个业务方法。如果在具体状态类中某些业务方法的实现完全相同,可以将这些方法移至抽象状态类,实现代码的复用。
// 抽象状态
class State
{
private:
Account * account;
public:
State(void);
State(Account * account);
virtual bool reduceIntegral(void) = 0;
virtual void raffle(void) = 0;
virtual void dispensePrize(void) = 0;
Account * getAccount(void);
void setAccount(Account * account);
};
State::State(void) : account(nullptr) {}
State::State(Account * account) : account(account) {}
Account * State::getAccount(void)
{
return account;
}
void State::setAccount(Account * account)
{
this->account = account;
}
具体状态类是抽象状态类的子类,每个子类实现一个与环境类的一个状态相关的行为。每个具体状态类对应环境类的一个具体状态,不同的具体状态类其行为有所不同。
// 具体状态,正常的状态
class NormalState : public State
{
public:
using State::State;
bool reduceIntegral(void) override;
void raffle(void) override;
void dispensePrize(void) override;
};
bool NormalState::reduceIntegral(void)
{
if (getAccount()->getIntegral() >= 160)
{
getAccount()->setIntegral(getAccount()->getIntegral() - 160);
std::cout << "原石扣除成功,剩余原石:【" << getAccount()->getIntegral() << "】" << std::endl;
getAccount()->changeState(new RaffleState(getAccount()));
return true;
}
else
{
std::cout << "原石不足,请充值。" << std::endl;
return false;
}
}
void NormalState::raffle(void)
{
std::cout << "正常状态下不能进行抽奖" << std::endl;
}
void NormalState::dispensePrize(void)
{
std::cout << "正常状态下不能领取奖品" << std::endl;
}
// 具体状态,抽奖状态
class RaffleState : public State
{
private:
static int firstPrizeCount;
static int secondPrizeCount;
public:
using State::State;
bool reduceIntegral(void) override;
void raffle(void) override;
void dispensePrize(void) override;
};
int RaffleState::firstPrizeCount = 0;
int RaffleState::secondPrizeCount = 0;
bool RaffleState::reduceIntegral(void)
{
std::cout << "抽奖状态下已经扣除过原石了" << std::endl;
return false;
}
void RaffleState::raffle(void)
{
firstPrizeCount++;
secondPrizeCount++;
int random_number = (std::rand() % 90) + 1; // 生成一个随机数
if (random_number == 90 || firstPrizeCount == 90)
{
getAccount()->changeState(new DispenseState(getAccount(), 1));
firstPrizeCount = 0;
}
else if (random_number % 10 == 0 || secondPrizeCount == 10)
{
getAccount()->changeState(new DispenseState(getAccount(), 2));
secondPrizeCount = 0;
}
else
{
getAccount()->changeState(new DispenseState(getAccount(), 3));
}
firstPrizeCount = (firstPrizeCount > 90) ? 0 : firstPrizeCount;
secondPrizeCount = (secondPrizeCount > 10) ? 0 : secondPrizeCount;
}
void RaffleState::dispensePrize(void)
{
std::cout << "抽奖状态下还不能领取奖品" << std::endl;
}
// 具体状态,发放奖品状态
class DispenseState : public State
{
private:
int kind;
public:
DispenseState(Account * account, int kind);
bool reduceIntegral(void) override;
void raffle(void) override;
void dispensePrize(void) override;
};
DispenseState::DispenseState(Account * account, int kind)
{
setAccount(account);
this->kind = kind;
}
bool DispenseState::reduceIntegral(void)
{
std::cout << "发放奖品状态下已经扣除过原石了" << std::endl;
return false;
}
void DispenseState::raffle(void)
{
std::cout << "发放奖品状态下已经进行过抽奖了" << std::endl;
}
void DispenseState::dispensePrize(void)
{
int random_number = 0;
switch (this->kind)
{
case 1:
random_number = (std::rand() % (sizeof(fiveStarAward) / sizeof(fiveStarAward[0]))); // 生成一个随机数
std::cout << "恭喜你获得【5 星角色】:【" << fiveStarAward[random_number] << "】" << std::endl;
break;
case 2:
random_number = (std::rand() % (sizeof(fourStarAward) / sizeof(fourStarAward[0]))); // 生成一个随机数
std::cout << "恭喜你获得【4 星角色】:【" << fourStarAward[random_number] << "】" << std::endl;
break;
case 3:
random_number = (std::rand() % (sizeof(threeStarAward) / sizeof(threeStarAward[0]))); // 生成一个随机数
std::cout << "恭喜你获得【3 星武器】:【" << threeStarAward[random_number] << "】" << std::endl;
break;
}
getAccount()->changeState(new NormalState(getAccount()));
}
环境类维持一个对抽象状态类的引用。通过 setState() 方法可以向环境类注入不同的状态对象,再在环境类的业务方法中调用状态对象的方法。
// 环境类
class Account
{
private:
std::string owner;
int integral;
State * state; // 抽象状态类的引用
public:
Account(void);
Account(std::string owner, int integral);
~Account(void);
void changeState(State * newState);
void lottery(void);
void recharge(int integral);
std::string getOwner(void);
void setOwner(std::string owner);
int getIntegral(void);
void setIntegral(int integral);
};
Account::Account(void) : integral(0), state(new NormalState(this)) {}
Account::Account(std::string owner, int integral) : integral(integral), state(new NormalState(this)) {}
Account::~Account(void)
{
if (state != nullptr)
{
delete state;
}
}
void Account::changeState(State * newState)
{
if (state != nullptr)
{
delete state;
}
state = newState;
}
void Account::lottery(void)
{
bool result = state->reduceIntegral();
if (result)
{
state->raffle();
state->dispensePrize();
}
std::cout << std::endl;
}
void Account::recharge(int integral)
{
if (integral > 0)
{
this->integral += integral;
}
}
std::string Account::getOwner(void)
{
return owner;
}
void Account::setOwner(std::string owner)
{
this->owner = owner;
}
int Account::getIntegral(void)
{
return integral;
}
void Account::setIntegral(int integral)
{
this->integral = integral;
}
环境类实际上是真正拥有状态的对象,这里只是将环境类中与状态有关的代码提取出来封装到专门的状态类中。环境类 Context 与抽象状态类 State 之间存在单向关联关系,在 Context 中定义了一个 State 对象。在实际使用时,它们之间可能存在更为复杂的关系,State 与 Context 之间可能也存在依赖或者关联关系。
在状态模式的使用过程中,一个对象的状态之间还可以进行相互转换。通常有以下两种实现状态转换的方式:
- 统一由环境类来负责状态之间的转换。此时,环境类还充当了状态管理器(State Manager)角色。在环境类的业务方法中通过对某些属性值的判断实现状态转换,还可以提供一个专门的方法用于实现属性判断和状态转换。
- 由具体状态类来负责状态之间的转换。可以在具体状态类的业务方法中判断环境类的某些属性值再根据情况为环境类设置新的状态对象,实现状态转换。同样,也可以提供一个专门的方法来负责属性值的判断和状态转换。此时,状态类与环境类之间将存在依赖或关联关系,因为状态类需要访问环境类中的属性值。
main() 函数:
#include <iostream>
#include <cstdlib> // 提供 rand() 和 srand()
#include <ctime> // 提供 time()
std::string fiveStarAward[] = {"温迪", "钟离", "雷电将军", "纳西妲", "芙宁娜", "那维莱特"};
std::string fourStarAward[] = {"行秋", "芭芭拉", "班尼特", "香菱", "诺艾尔", "安柏"};
std::string threeStarAward[] = {"黎明神剑", "弹弓", "白缨枪", "讨龙英杰谭", "魔导绪论", "旅行剑", "黑缨枪", "吃虎鱼刀"};
int main(void)
{
std::srand(static_cast<unsigned int>(std::time(nullptr))); // 设置随机数种子为当前时间
Account account("荧", 1600);
for (int i = 0; i < 10; i++)
{
account.lottery();
}
account.lottery();
account.recharge(16800);
for (int i = 0; i < 10; i++)
{
account.lottery();
}
return 0;
}
三、状态模式的总结
状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中。通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端而言是透明的,方便了客户端的使用。
3.1、状态模式的优点
- 封装了状态的转换规则。在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
- 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块。状态模式可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
3.2、状态模式的缺点
- 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
- 状态模式的程序结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
- 状态模式对开闭原则的支持并不太好。增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
3.3、状态模式的适用场景
- 对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化。
- 在代码中包含大量与对象状态有关的条件语句。这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)