状态(State)模式
一、问题的提出
糖果机的CEO正在使用一款软件,现在需要让糖果机控制器如下图般工作:![]()
二、一般设计思路
1、找出所有状态
没有25分钱、有25分钱、糖果售罄、售出糖果2、创建实例变量来持有目前的状态,定义每个状态的值
#define SOLD_OUT 0//糖果售罄 #define NO_QUARTER 1//没有25分钱 #define HAS_QUARTER 2//有25分钱 #define SOLD 3//售出糖果
3、创建状态机类
现在,我们创建一个类,它的作用对象就是一个状态机。对每一个动作,我们都创建了一个对应的方法,这些方法利用条件语句来决定在每个状态内什么行为是恰当的。比如对“投入25分钱”这个动作来说,我们可以把对应的方法写成下面的养子:
//投入硬币动作 void insertQuarter() { if(HAS_QUARTER == state) { //当前状态是“有硬币” cout<<"You can't insert another quarter"<<endl; } else if(SOLD_OUT == state) { //当前状态是“糖果售罄” cout<<"You can't insert a quarter, the machine is sold out"<<endl; } else if(SOLD == state) { //当前状态是“售出糖果” cout<<"Please wait,we're already giving you a gumball"<<endl; } else if(NO_QUARTER == state) { //当前状态是“没有硬币” state = HAS_QUARTER; cout<<"You inserted a quarter"<<endl; } }
4、实现代码
以上就是糖果机的类。实现类中的函数就行了。#define SOLD_OUT 0 #define NO_QUARTER 1 #define HAS_QUARTER 2 #define SOLD 3 //糖果机类 class GumballMachine { public: GumballMachine(int count);//构造函数,传入糖果数量 void insertQuarter();//顾客投入25分硬币状态 void ejectQuarter();//顾客要求退回硬币状态 void turnCrank();//转动曲柄状态 void dispense();//发放糖果状态 };
5、变更请求
现在需要将“购买糖果”变成一个游戏:当曲柄被转动时,有10%的几率掉下来的是两颗糖果。
由以上考虑周详的方法写糖果机代码,但是不容易扩展,因为在投币、退币、转动曲柄、发放糖果等状态需要添加代码。turnCrack()尤其会变得很乱,因为必须加上代码来检查目前的顾客是否是赢家,然后再决定是切换到赢家状态还是售出糖果状态。
因此,以上的糖果类不能再继续使用,但是应该怎么变化呢??封装变化。
三、新的设计
3.1设计方式
新的设计计划是这样的:不要维护现有代码,重写它以便将状态对象封装在各自的类中,然后在动作发生时委托给当前状态。
因此,需要做的事情是:
1、定义一个State接口。在这个接口内,糖果机的每个动作都有一个对应的方法。
2、为机器中的每个状态实现状态类。这些类将负责在对应的状态下进行机器的行为。
3、最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类。
定义状态接口和类:
将所有的状态直接映射到类。
3.2实现状态类和糖果机类
头文件:
#ifndef STATE__H #define STATE__H #include <iostream> using namespace std; //状态超类 class State { public: virtual void insertQuarter() = 0;//顾客投入25分硬币状态 virtual void ejectQuarter() = 0;//顾客要求退回硬币状态 virtual void turnCrank() = 0;//转动曲柄状态 virtual void dispense() = 0;//发放糖果状态 }; //没有25分钱状态 class NoQuarterState : public State { private: GumballMachine* gumbalMachine; public: NoQuarterState(GumballMachine* gumbalMachine); void insertQuarter();//顾客投入25分硬币状态 void ejectQuarter();//顾客要求退回硬币状态 void turnCrank();//转动曲柄状态 void dispense();//发放糖果状态 }; //售出状态 class SoldState : public State { private: GumballMachine* gumbalMachine; public: SoldState(GumballMachine* gumbalMachine); void insertQuarter();//顾客投入25分硬币状态 void ejectQuarter();//顾客要求退回硬币状态 void turnCrank();//转动曲柄状态 void dispense();//发放糖果状态 }; //有25分钱状态 class HasQuarterState : public State { private: GumballMachine* gumbalMachine; public: HasQuarterState(GumballMachine* gumbalMachine); void insertQuarter();//顾客投入25分硬币状态 void ejectQuarter();//顾客要求退回硬币状态 void turnCrank();//转动曲柄状态 void dispense();//发放糖果状态 }; //售罄状态 class SoldOutState : public State { private: GumballMachine* gumbalMachine; public: SoldOutState(GumballMachine* gumbalMachine); void insertQuarter();//顾客投入25分硬币状态 void ejectQuarter();//顾客要求退回硬币状态 void turnCrank();//转动曲柄状态 void dispense();//发放糖果状态 }; //糖果机类 class GumballMachine { private: State* soldOutState;//糖果售罄 State* noQuarterState;//没有25分钱 State* hasQuarterState;//有25分钱 State* soldState;//售出糖果 State* state;//糖果机当前状态 int count; //糖果数目 public: GumballMachine(int numberGumballs); void insertQuarter(); void ejectQuarter(); void turnCrank(); void dispense(); //各个状态的get方法 State* getNoQuarterState(); State* getSoldOutState(); State* getSoldState(); State* getHasQuarterState(); void SetState(State* state); //获取糖果数目 int getCount(); void releaseBall(); }; #endif
#include "GumballMachine.h" //NoQuarterState状态类实现 NoQuarterState::NoQuarterState(GumballMachine* gumbalMachine)//通过构造函数得到糖果机引用,然后将它记录在实例变量 { this->gumbalMachine = gumbalMachine; } void NoQuarterState::insertQuarter() { cout<<"You inserted a quarter"<<endl; this->gumbalMachine->SetState(gumbalMachine->getHasQuarterState()); } void NoQuarterState::ejectQuarter() { cout<<"You haven't insert a quarter"<<endl; } void NoQuarterState::turnCrank() { cout<<"You turned, but there's no quarter"<<endl; } void NoQuarterState::dispense() { cout<<"You need to pay first"<<endl; } //HasQuarterState状态类实现 HasQuarterState::HasQuarterState(GumballMachine* gumbalMachine) { this->gumbalMachine = gumbalMachine; } void HasQuarterState::insertQuarter() { cout<<"You can't insert another quarter"<<endl; } void HasQuarterState::ejectQuarter() { cout<<"Quarter returned"<<endl; this->gumbalMachine->SetState(gumbalMachine->getNoQuarterState()); } void HasQuarterState::turnCrank() { cout<<"You turned..."<<endl; this->gumbalMachine->SetState(this->gumbalMachine->getSoldState()); } void HasQuarterState::dispense() { cout<<"No gumball dispensed"<<endl; } //SoldState状态类实现 SoldState::SoldState(GumballMachine* gumbalMachine) { this->gumbalMachine = gumbalMachine; } void SoldState::insertQuarter() { cout<<"Please wait, we're already giving you a gumball"<<endl; } void SoldState::ejectQuarter() { cout<<"Sorry, you already turned the crank"<<endl; } void SoldState::turnCrank() { cout<<"Turning twice doesn't get you another gumball"<<endl; } void SoldState::dispense() { this->gumbalMachine->releaseBall(); if(gumbalMachine->getCount() > 0) gumbalMachine->SetState(gumbalMachine->getNoQuarterState()); else { cout<<"Oops , out of gumballs!"<<endl; gumbalMachine->SetState(gumbalMachine->getSoldOutState()); } } GumballMachine::GumballMachine(int numberGumballs)//糖果机构造函数 { soldOutState = new SoldOutState(this); noQuarterState = new NoQuarterState(this); hasQuarterState = new HasQuarterState(this); soldOutState = new SoldOutState(this); this->count = numberGumballs; if(numberGumballs > 0 )//超过0颗糖果,状态设为NoQuarterState state = noQuarterState; } void GumballMachine::insertQuarter() { state->insertQuarter(); } void GumballMachine::ejectQuarter() { state->ejectQuarter(); } void GumballMachine::turnCrank() { state->turnCrank(); state->dispense();//注意,我们不需要在糖果机种准备一个dispense动作的方法,因为这只是一个内部动作;用户可以不直接要求机器发送糖果。但是我们是在状态对象的turnCrank方法中调用dispense方法的 } void GumballMachine::SetState(State* state) { this->state = state; } //获取每个状态的方法 State* GumballMachine::getNoQuarterState() { return noQuarterState; } State* GumballMachine::getHasQuarterState() { return hasQuarterState; } State* GumballMachine::getSoldOutState() { return soldOutState; } State* GumballMachine::getSoldState() { return soldState; } int GumballMachine::getCount() { return count; } void GumballMachine::releaseBall() { cout<<"A gumball comes rolling out the slot..."<<endl; if(count != 0) count--; }
四、定义状态模式
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。这个描述中的第一部分附有相当多的含义,因为这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态改变而改变。而这个定义中的第二部分“看起来好像修改了它的类”是什么意思呢?从客户的视角来看:如果说你使用的对象能够完全改变它的行为,那你会觉得这个对象实际上是从别的类实例化而来的。然而,实际上是在使用组合通过简单引用不同的状态来造成类改变的假象。