C++设计模式_行为型模式_状态模式State
状态(State)模式是一种行为型模式,其实现可完成类似有限状态机的功能。换句话说,一个对象可以处于多种不同的状态(当然,同一时刻只能处于某一种状态),从而让对象产生不同的行为。通过状态模式可以表达出这些不同的状态并实现对象在这些状态之间的转换。状态模式最突出的特点是用类来表示状态,这一点与策略模式有异曲同工之妙(策略模式是用类来表示策略)。状态模式与策略模式从UML图上看完全相同,只不过两者所运用的场合以及所表达的目的不同。
要理解状态模式,可以设想生活中的人在日常生活中的状态,新的一天刚开始工作时精神饱满,随着时间推移,逐渐从精神状态变为饥饿状态,吃了饭之后,状态变为困倦,睡了一觉之后又恢复到了精神饱满状态,这个过程的状态装换如下:
一个基本的状态转换范例
下面还是以闯关类打斗游戏开发为例,游戏主角的主要人物是杀怪,为简单起见,约定怪物初始化时生命值为500,怪物的状态转换如下:
(1) 当怪物血量大于400点时,怪物处于凶悍状态,此时怪物对主角发起攻击;
(2) 当怪物血量小于400,大于100时,怪物处于不安状态,此时怪物对主角发起反攻;
(3) 当怪物血量小于100点时,怪物处于恐惧状态,此时怪物开始逃命。
(4) 当怪物血量小于等于0点时,怪物处于死亡状态。
代码实现如下:
enum class MonsterState{// 怪物满血时处于凶悍状态,依次递减Monster_Fierce, // 凶悍, m_life > 400Monster_Worry, // 不安, 100 < m_life < 400 Monster_Fear, // 恐惧 0 < m_life < 100 Monster_Dead, // 死亡 };class Monster{public:Monster(int life) : m_life(life), m_state(MonsterState::Monster_Fierce) // 初始状态为凶悍{}public:void Attacked(int power) // 怪物被攻击时,power表示怪物被攻击的血量,也是怪物掉的血量{m_life -= power; // 怪物剩余血量,判断怪物状态if (m_state == MonsterState::Monster_Fierce) // 怪物血量 > 400{if (m_life > 400){cout << "怪物收到了 " << power << "攻击,怪物处于凶悍状态,对主角发起攻击" << endl;}else if (m_life > 100){cout << "怪物受到" << power << "点伤害,状态变为不安" << endl;m_state = MonsterState::Monster_Worry;}else if (m_life > 0){cout << "怪物变得恐惧,并开始逃跑" << endl;m_state = MonsterState::Monster_Fear;}else{cout << "怪物受到了" << power << "点伤害,死亡" << endl;m_state = MonsterState::Monster_Dead;}}else if (m_state == MonsterState::Monster_Worry) // 怪物血量 100 < m_life < 400{if (m_life > 100){cout << "怪物受到" << power << "点伤害,状态变为不安,对主角发起反攻!" << endl;}else if (m_life > 0){cout << "怪物变得恐惧,并开始逃跑" << endl;m_state = MonsterState::Monster_Fear;}else{cout << "怪物受到了" << power << "点伤害,死亡" << endl;m_state = MonsterState::Monster_Dead;}}else if (m_state == MonsterState::Monster_Fear) // 怪物血量 0 < m_life < 100{if (m_life > 0){cout << "怪物变得恐惧,并开始逃跑" << endl;}else{cout << "怪物受到了" << power << "点伤害,死亡" << endl;m_state = MonsterState::Monster_Dead;}}else // 怪物血量 0{cout << "怪物死亡" << endl;}}private:int m_life;MonsterState m_state;};void test(){// 用类表示状态// 1. 定义一个抽象基类Monster* monster = new Monster(500);cout << "怪物刚出生,当前处于凶悍状态" << endl;monster->Attacked(20);monster->Attacked(110);monster->Attacked(300);}
怪物初始状态为凶悍状态,随着不断攻击,最后是死亡状态;详细分析一下怪物的状态转换。
刚开始怪物的血量大于500点,处于凶悍状态。如果主角第一次攻击怪物小于100点血量,怪物仍然为凶悍状态;如果怪物被攻击100-200点血量,怪物剩余血量为100-400点之间,怪物从凶悍状态变为不安状态;如果怪物被攻击的血量只剩下0-100点时,怪物直接从凶悍状态转为恐惧状态;如果怪物被攻击的血量小于0时,从凶悍状态转变为死亡状态。
改造上面的代码
从上述代码可以看到,Attacked的实现逻辑比较复杂,主要问题有以下几点:
(1) 其中用到了诸多的if…else…语句来进行各种条件判断。
(2) 诸如反击、呼唤支援、逃跑、死亡后续处理等事宜可能会涉及相当多的业务逻辑代码编写工作,这些业务逻辑代码如果一并放在Attacked成员函数中实现,可能会导致Attacked成员函数的实现代码达到数百甚至数千行,非常难以维护。(不满足开闭原则)
(3) 如果日后为怪物增加新的状态,那么又会导致增加Attacked中if…else…语句的条件判断,进一步加剧了Attacked的维护难度。
通过状态模式, 可以对上述的代码进行改造。在状态模式中,怪物的每个状态都写成一个状态类(类似的情形,例如在策略模式中是将每个策略写成一个策略类),当然,应该为这些状态类抽象出一个统一的父类以便实现多态,然后在每个状态类中实现相关的业务逻辑,例如,对于怪物的“不安”状态可以实现为一个名字叫作Status_Worr的类,在该类中实现相关的业务逻辑,例如怪物对主角的反击和呼唤支援。这样就相当于把上述Monster 类的Attacked成员函数的业务逻辑代码拆分到各个状态类中去实现,不但大大简化了Attacked成员函数的实现代码,也实现了委托机制,即Attacked成员函数把本该自己实现的功能委托给了各个状态类(中的成员函数)去实现。当然,必须持有该类的一个指针,才能把功能委托给该类。
怪物状态类:
// 怪物状态类的基类class MonsterStatus{public:virtual void Attacked(int power) = 0;virtual ~MonsterStatus(){}};// 凶悍状态类class MonsterStatusFerocious : public MonsterStatus{public:virtual void Attacked(int power) override{cout << "怪物进入凶悍状态,攻击力翻倍" << endl;}};//不安状态类 class MonsterStatusWorry : public MonsterStatus{public:virtual void Attacked(int power) override{cout << "怪物进入不安状态,攻击力减半" << endl;}};// 恐惧状态类class MonsterStatusFear : public MonsterStatus{public:virtual void Attacked(int power) override{cout << "怪物进入恐惧状态,攻击力为0" << endl;}};// 死亡状态class MonsterStatusDead : public MonsterStatus{virtual void Attacked(int power) override{cout << "怪物已经死亡,无法攻击" << endl;}};// 怪物类中,状态使用类,初始为凶悍状态class Monster{public:Monster(int life) : m_life(life), m_status(new MonsterStatusFerocious()) {}~Monster(){delete m_status;}void Attacked(int power){int origin_life = m_life;m_life -= power;if (origin_life > 400){if (m_life > 400){m_status->Attacked(power);}else if (m_life > 100){delete m_status;m_status = new MonsterStatusWorry();m_status->Attacked(power);}else if (m_life > 0){delete m_status;m_status = new MonsterStatusFear();m_status->Attacked(power);}else{delete m_status;m_status = new MonsterStatusDead();m_status->Attacked(power);}}else if (origin_life > 100){if (m_life > 100){m_status->Attacked(power);}else if (m_life > 0){delete m_status;m_status = new MonsterStatusFear();m_status->Attacked(power);}else{delete m_status;m_status = new MonsterStatusDead();m_status->Attacked(power);}}else if (origin_life > 0){if (m_life > 0){delete m_status;m_status = new MonsterStatusFear();m_status->Attacked(power);}else{delete m_status;m_status = new MonsterStatusDead();m_status->Attacked(power);}}else{delete m_status;m_status = new MonsterStatusDead();m_status->Attacked(power);}}private:int m_life;MonsterStatus * m_status;};
上面代码,通过将业务类逻辑代码委托给状态类,可以有效减少Monster类Attacked成员函数的代码量,这就是状态模式存在的价值—使业务逻辑代码更加清晰和易于维护。
引入状态模式
定义:允许一个对象(怪物)在其内部状态改变(比如从凶悍状态改变为不安状态)时改变它的行为(比如从疯狂反击变为反击并互换支援),
对象看起来似乎修改了它的类。
UML图中的三种角色
a)Context(环境类):指Monster类。
b)State(抽象状态类):指MonsterStatus子类。
c)ConcreteState(具体状态类):指MonsterStatus_Feroc、MonsterStatus_Worr、MonsterStatus_Fear、MonsterStatus_Dead
如下两种情况下,可以考虑使用状态模式:
a)对象行为取决于其状态,该对象需要根据其状态来改变行为。
b)一个操作中含有庞大的条件分支语句,而这些分支语句的执行依赖于对象的当前状态。
改造为单例模式
class Monster;// 状态父类class MonsterStatus{public:virtual void Attacked(int power, Monster* mainobj) = 0;virtual ~MonsterStatus(){}};class MonsterStatus_Feroc;class MonsterStatus_Worr;class MonsterStatus_Fear;class MonsterStatus_Dead;// 死亡状态class MonsterStatus_Dead : public MonsterStatus{public:static MonsterStatus_Dead* getInstance();void Attacked(int power, Monster* mainobj) override;};// 恐惧状态class MonsterStatus_Fear : public MonsterStatus{public:static MonsterStatus_Fear* getInstance();void Attacked(int power, Monster* mainobj) override;};// 不安状态class MonsterStatus_Worr : public MonsterStatus{public:static MonsterStatus_Worr* getInstance();void Attacked(int power, Monster* mainobj) override;};// 凶悍状态class MonsterStatus_Feroc : public MonsterStatus{public:static MonsterStatus_Feroc* getInstance();void Attacked(int power, Monster* mainobj) override;};// 怪物类class Monster{public:explicit Monster(int life): m_life(life), m_pState(MonsterStatus_Feroc::getInstance()) // 初始为凶悍{}~Monster() = default;int GetLife() { return m_life; }void SetLife(int life) { m_life = life; }MonsterStatus* getCurrentState() { return m_pState; }void setCurrentState(MonsterStatus* s) { m_pState = s; }void Attacked(int power) // 怪物被攻击{m_pState->Attacked(power, this);}private:int m_life{ 0 }; // 血量MonsterStatus* m_pState{ nullptr }; // 当前状态};// 单例获取inline MonsterStatus_Dead* MonsterStatus_Dead::getInstance(){static MonsterStatus_Dead instance;return &instance;}inline MonsterStatus_Fear* MonsterStatus_Fear::getInstance(){static MonsterStatus_Fear instance;return &instance;}inline MonsterStatus_Worr* MonsterStatus_Worr::getInstance(){static MonsterStatus_Worr instance;return &instance;}inline MonsterStatus_Feroc* MonsterStatus_Feroc::getInstance(){static MonsterStatus_Feroc instance;return &instance;}// 各状态行为实现inline void MonsterStatus_Dead::Attacked(int power, Monster* mainobj){if (mainobj->GetLife() > 0){mainobj->SetLife(mainobj->GetLife() - power);}cout << "怪物死亡" << endl;}inline void MonsterStatus_Fear::Attacked(int power, Monster* mainobj){int org = mainobj->GetLife();if (org - power > 0){mainobj->SetLife(org - power);cout << "怪物处于恐惧状态中,处于逃跑之中!" << endl;}else{mainobj->setCurrentState(MonsterStatus_Dead::getInstance());mainobj->getCurrentState()->Attacked(power, mainobj);}}inline void MonsterStatus_Worr::Attacked(int power, Monster* mainobj){int org = mainobj->GetLife();if (org - power > 100){mainobj->SetLife(org - power);cout << "怪物处于不安状态中,对主角进行反击并呼唤支援!" << endl;}else{mainobj->setCurrentState(MonsterStatus_Fear::getInstance());mainobj->getCurrentState()->Attacked(power, mainobj);}}inline void MonsterStatus_Feroc::Attacked(int power, Monster* mainobj){int org = mainobj->GetLife();if (org - power > 400){mainobj->SetLife(org - power);cout << "怪物处于凶悍状态,对主角发起攻击" << endl;}else{mainobj->setCurrentState(MonsterStatus_Worr::getInstance());mainobj->getCurrentState()->Attacked(power, mainobj);}}void test(){Monster monster(500);cout << "怪物出生,当前处于凶悍状态" << endl;monster.Attacked(20);monster.Attacked(100);monster.Attacked(200);monster.Attacked(170);monster.Attacked(100);monster.Attacked(100);/*怪物出生,当前处于凶悍状态怪物处于凶悍状态,对主角发起攻击怪物处于不安状态中,对主角进行反击并呼唤支援!怪物处于不安状态中,对主角进行反击并呼唤支援!怪物处于恐惧状态中,处于逃跑之中!怪物死亡怪物死亡*/}