状态模式,就是把所有的状态抽象成一个个具体的类,然后继承一个抽象状态类,在每一个状态类内封装对应状态的行为,符合开放封闭原则,当增加新的状态或减少状态时,只需修改关联的类即可。很适合多分支行为方法的处理,这里的多分支,当然是状态比较多的情况下,如果只有小于4个状态,个人认为还是分支处理简单些。
状态模式正规的定义与类图(引用《大话设计模式》)如下所示:
这里以一天工作中的工作状态为例实现状态模式。
工作状态的类图结构(引用《大话设计模式》)如下所示:
C++代码实现如下:
#include <iostream>
#include <memory>//*********************State Pattern*******************
class Work;//抽象状态类
class State
{
public:virtual void WriteProgram(Work* ptrWork) = 0;
};class ForenoonState;//工作类
class Work
{
private:std::shared_ptr<State> smartState;double hour;bool finish;public:Work();void SetHour(const double h){hour = h;}double GetHour() const{return hour;}void SetFinish(bool bFinish){finish = bFinish;}bool GetFinish() const{return finish;}void SetState(std::shared_ptr<State> pState){smartState = pState;}std::shared_ptr<State> GetState() const{return smartState;}void WriteProgram(){smartState->WriteProgram(this);}};//睡眠状态
class SleepingState : public State
{
public:void WriteProgram(Work* ptrWork){std::cout << "当前时间:" << ptrWork->GetHour() << "点 撑不住了,睡觉吧!" << std::endl;}
};//下班休息状态
class RestState : public State
{
public:void WriteProgram(Work* ptrWork){std::cout << "当前时间:" << ptrWork->GetHour() << "点 下班回家了!" << std::endl;}
};//傍晚工作状态
class EveningState : public State
{
public:void WriteProgram(Work* ptrWork){if (ptrWork->GetFinish()){ptrWork->SetState(std::make_shared<RestState>());ptrWork->WriteProgram();return;}if (ptrWork->GetHour() < 21){std::cout << "当前时间:" << ptrWork->GetHour() << "点 加班吆,疲惫啊!" << std::endl;}else{//超过21点,转换到睡眠状态ptrWork->SetState(std::make_shared<SleepingState>());ptrWork->WriteProgram();}}
};//下午工作状态
class AfternoonState : public State
{
public:void WriteProgram(Work* ptrWork){if (ptrWork->GetHour() < 17){std::cout << "当前时间:" << ptrWork->GetHour() << "点 下午状态还不错,继续努力!" << std::endl;}else{//超过17点,转换傍晚工作状态ptrWork->SetState(std::make_shared<EveningState>());ptrWork->WriteProgram();}}
};//中午工作状态
class NoonState : public State
{
public:void WriteProgram(Work* ptrWork){if (ptrWork->GetHour() < 13){std::cout << "当前时间:" << ptrWork->GetHour() << "点 饿了,午饭,犯困,午休!" << std::endl;}else{//超过13点,转换下午工作状态ptrWork->SetState(std::make_shared<AfternoonState>());ptrWork->WriteProgram();}}
};//上午工作状态类
class ForenoonState : public State
{
public:void WriteProgram(Work* ptrWork){if (ptrWork->GetHour() < 12){std::cout << "当前时间:" << ptrWork->GetHour() << "点 上午工作,精神百倍!" << std::endl;}else{//超过12点,转换中午工作状态ptrWork->SetState(std::make_shared<NoonState>());ptrWork->WriteProgram();}}
};Work::Work() : hour(0), finish(false), smartState(std::make_shared<ForenoonState>()) {}//************************Test**************************
int main()
{std::shared_ptr<Work> work = std::make_shared<Work>();work->SetHour(9);work->WriteProgram();work->SetHour(10);work->WriteProgram();work->SetHour(12);work->WriteProgram();work->SetHour(13);work->WriteProgram();work->SetHour(14);work->WriteProgram();work->SetHour(17);work->WriteProgram();work->SetFinish(false);//work->SetFinish(true);//work->WriteProgram();work->SetHour(19);work->WriteProgram();work->SetHour(22);work->WriteProgram();system("pause");return 0;
}
实际题例:
题例:万能糖果公司
我们认为糖果机的控制器需要如下图般的工作,希望你能用C++语言帮我们实现它,而且需要让设计能够尽量有弹性而且好维护,因为将来我们可能要为它增加更多的行为。 ![[Pasted image 20230413215937.png]]
这张图是一个状态图,糖果机的所有状态有:“没有25分钱”,“有25分钱”,“糖果售罄”,“售出糖果”,每一个状态都代表机器不同的配置,需要某些动作将目前的状态转换到另外一个状态,要进入另外一种状态,必须做某些事情。对于糖果机当前任何一个动作,我们都需要检查,看看糖果机所处的状态和动作是否合适。
下面我们按照一般思维来设计糖果机的这些功能,步骤如下:
(1)找出所有的状态
“没有25分钱”、“有25分钱”、“糖果售罄”、“售出糖果”
(2)创建一个实例变量来持有目前的状态,然后定义每个状态的值
const static int NO_QUARTER = 1;
const static int HAS_QUARTER = 2;
const static int SOLD_OUT = 0;
const static int SOLD = 3;
int state = SOLD_OUT;
3)将所有糖果机系统中可以发生的动作整合起来
根据状态图可知,四个状态对应着四个动作:“投入25分钱”,“退回25分钱”,“转动曲柄”,“发放糖果”;
这些动作是糖果机的对客户的接口,糖果机会以机器按钮的方式让这些接口与玩家交互,这是你能对糖果机做的事情,调用任何一个动作都会造成状态的转换,发放糖果更多是糖果机的内部动作,机器自己调用自己。
(4)现在,我们创建了一个类,它的作用就像是一个状态机,对每一个动作,我们都创建了一个对应的方法,这些方法利用条件语句来决定在每个状态内什么行为是恰当的。每一个可能的状态都需要用条件语句检查,然后对每一个可能的状态展现适当的行为。
#include<iostream>
using namespace std;
class GumballMachine {
private:const static int SOLD_OUT = 0; /* 售罄状态 */const static int NO_QUARTER = 1; /* 没有25分钱状态 */const static int HAS_QUARTER = 2; /* 有25分钱状态 */const static int SOLD = 3; /* 售出状态 */int state = SOLD_OUT; /* 糖果机拆箱状态初始化为售罄状态,等待工人装入糖果 */int count = 0; /* 糖果机当前糖果数量 */
public:/* 构造函数初始化,装入糖果后,就需要改变糖果机当前状态为 "没有25分钱状态" */GumballMachine(int count) {this->count = count;if (count > 0) {state = NO_QUARTER; }}/* 投入25分钱 */void insertQuarter() {if (state == HAS_QUARTER) {cout << "You can't insert another quarter" << endl;} else if (state == NO_QUARTER) {state = HAS_QUARTER; /* 只有当前状态为无25分钱状态才能执行投币操作 */cout << " You inserted a quarter"<<endl;} else if (state == SOLD_OUT) {cout << "You can't insert a quarter, the machine is sold out" <<endl;} else if (state == SOLD) {cout << "Please wait, we're already giving you a gumball" << endl;} }/* 退回25分钱 */void ejectQuarter() {if (state == HAS_QUARTER) {cout<< "Quarter returned" <<endl;state = NO_QUARTER;} else if (state == NO_QUARTER) {cout << "You haven't inserted a quarter, we can't return quarter to you" << endl;} else if (state == SOLD) {cout << "you already turned the crank, we can't return quarter to you" << endl;} else if (state == SOLD_OUT) {cout << "You can't eject,you haven't inserted a quarter"<<endl;} }/* 转动曲柄 */void turnCrank() {if (state == HAS_QUARTER) {cout << "You turned the crank..." << endl;state = SOLD;dispense();} else if (state == SOLD_OUT) {cout<< "You turned, but there is no gumball" <<endl;} else if (state == SOLD) {cout << "Turning twice doesn't get you another gumball!"<<endl;} else if (state == NO_QUARTER){cout<<"You turned, but there is no quarter"<<endl;}}/* 发放糖果 */void dispense(){if (state == HAS_QUARTER){cout<<"No gumball dispensed"<<endl;} else if (state == SOLD_OUT) {cout << "No gumball dispensed" << endl;} else if (state == SOLD) {cout<< "A gumball comes rolling out the slot"<<endl;count--;if (count == 0) {cout<<"Oops, Out of gumball! "<<endl;state = SOLD_OUT;} else {state = NO_QUARTER;}} else if (state == NO_QUARTER) {cout <<"You need to pay first"<<endl;}}
};
上述代码if-else多么的可怕,主要是如果糖果机器以后状态增多,那么修改if-else将是一个巨大的工作量,如果采用状态模式,在动作发生时委托给当前状态,将是一个不错的选择,主要分为以下3个步骤:
(1)定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法;
(2)为机器的每个状态实现状态类,这些状态类负责在对应的状态下进行机器的行为;
(3)摆脱老代码束缚,取而代之的方式将动作委托到状态类。
完整代码见下:
class GumballMachine;
/* 抽象接口,在此接口内,糖果机的每个动作都有一个对应的方法 */
class State{
public:virtual void insertQuarter() = 0; /* 投入25分钱 */virtual void ejectQuarter() = 0; /* 退回25分钱 */virtual void turnCrank() = 0; /* 转动曲柄 */virtual void dispense() = 0; /* 发放糖果 */
};/* 没有25分钱状态:根据状态图,当前状态只能执行"投入25分钱"的动作 */
class NoQuarterState:public State {
private:GumballMachine *gumballMachine;
public:NoQuarterState(GumballMachine *gumballMachine) {this->gumballMachine = gumballMachine;}/* 当前状态下,可以投币,机器状态根据状态图改变 */void insertQuarter() {cout << "You inserted a quarter" << endl;gumballMachine->setState(gumballMachine->getHasQuarterState());}/* 不恰当操作 */void ejectQuarter() { cout << "You have not inserted a quarter"<<endl;}/* 不恰当操作 */void turnCrank() { cout << "You turned,but there is no quarter" << endl;}/* 不恰当操作 */void dispense() {cout << "You need to pay quarter first."<<endl;}
};/* 有25分钱状态:根据状态图,当前状态可以执行"退回25分钱"和"转动曲柄"的动作 */
class HasQuarterState:public State {
private:GumballMachine *gumballMachine;
public:HasQuarterState(GumballMachine *gumballMachine) {this->gumballMachine = gumballMachine;}/* 不恰当操作 */void insertQuarter() {cout << "You can't insert another quarter." << endl;}/* 当前状态下,可以退出25分钱,状态需要根据状态图改变 */void ejectQuarter() {cout << "Quarter returned" << endl;gumballMachine->setState(gumballMachine->getNoQuarterState());}/* 当前状态下,可以转动曲柄,状态需要根据状态图改变 */void turnCrank() {cout << "You turned the Crank..." << endl;gumballMachine->setState(gumballMachine->getSoldState());}/* 不恰当操作 */void dispense() {cout << "No gumball disoensed"<<endl;}
};/* 售出糖果状态:根据状态图,当前状态只能执行"发放糖果"的动作 */
class SoldState:public State {
private:GumballMachine *gumballMachine;
public:SoldState(GumballMachine *gumballMachine) {this->gumballMachine = gumballMachine;}/* 不恰当操作 */void insertQuarter() {cout << "Pleaae wait, we're already giving you a gunball" << endl;}/* 不恰当操作 */void ejectQuarter() {cout << "Sorry, you aleady turned the crank." << endl;}/* 不恰当操作 */void turnCrank() {cout << "Turning twice doesn't get you another gunball" << endl;}/* 当前状态可以放糖果 */void dispense() {gumballMachine->releaseBall();if (gumballMachine->getCount() > 0){gumballMachine->setState(gumballMachine->getNoQuarterState());} else {gumballMachine->setState(gumballMachine->getSoldState());}}
};/* 售罄状态:根据状态图,当前状态都不能执行,只有等着人员给机器装糖果 */
class SoldOutState:public State {
private:GumballMachine *gumballMachine;
public:SoldOutState(GumballMachine *gumballMachine) {this->gumballMachine = gumballMachine;}void insertQuarter() {cout << "you can't insert a quarter, the machine is sold out" << endl;}void ejectQuarter() {cout << "you can't insert a quarter, you have not inserted a quarter yet" << endl;}void turnCrank() {cout << "you turned, but there no gumballs" << endl;}void dispense() {cout << "No gumball dispensed" << endl;}
};class GumballMachine{
private:State *soldOutState;State *noQuarterState;State *hasQuarterState;State *soldState;State *state = soldOutState;int count = 0;
public:GumballMachine(int count) {soldOutState = new SoldOutState(this);noQuarterState = new NoQuarterState(this);hasQuarterState = new HasQuarterState(this);soldOutState = new SoldState(this);this->count = count;if (count > 0) {state = noQuarterState;//糖果机初始状态}}void insertQuarter() {state->insertQuarter();}void ejectQuarter() {state->ejectQuarter();}void turnCrank(){state->turnCrank();state->dispense();}void setState(State *state){this->state = state;}void releaseBall(){cout << "A gumball comes rolling out the slot"<<endl;if (count != 0){count = count - 1;}}State* getSoldState() {return soldState;}State* getHasQuarterState() {return hasQuarterState;}State* getNoQuarterState() {return noQuarterState;}State* getSoldOutState() {return soldOutState;}int getCount(){return count;}
};
四、状态模式分析
(1)状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为。
(2)状态模式的关键是引入了一个抽象类来专门表示对象的状态,这个类我们叫做抽象状态类,而对象的每一种具体状态类都继承了该类,并在不同具体状态类中实现了不同状态的行为,包括各种状态之间的转换。
五、适用场景
(1)行为随状态改变而改变的场景。
(2)条件、分支语句的代替者。
六、模式优点
(1)实现多态行为的好处是显而易见的,并且很容易添加状态来支持额外的行为。
(2)在状态模式中,对象的行为是其状态中函数的结果,并且在运行时根据状态改变行为,这就消除了对switch/case 或 if/else 条件逻辑的依赖。
(3)可以提高内聚性,因为状态特定的行为被聚合到具体的类中,这些类被放在代码中的一个位置。