This post implements state design patter in <Head First Design Pattern>. The state pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. In plain words, when the run-time behavior depends on state and state will change according to behavior at run-time, perhaps we need to consider state pattern.
The State pattern is a solution to the problem of how to make behavior depend on state.
- Define a "context" class to present a single interface to the outside world.
- Define a State abstract base class.
- Represent the different "states" of the state machine as derived classes of the State base class.
- Define state-specific behavior in the appropriate State derived classes.
- Maintain a pointer to the current "state" in the "context" class.
- To change the state of the state machine, change the current "state"pointer.
The State pattern does not specify where the state transitions will be defined. The choices are two: the "context" object, or each individual State derived class. The advantage of the latter option is ease of adding new State derived classes. The disadvantage is each State derived class has knowledge of (coupling to) its siblings, which introduces dependencies between subclasses.
To implement the design, we need to
- Identify an existing class, or create a new class, that will serve as the "state machine" from the client's perspective.That class is the "wrapper" class.
- Create a State base class that replicates the methods of the state machine interface. Each method takes one additional parameter:an instance of the wrapper class. The State base class specifies any useful "default" behavior.
- Create a State derived class for each domain state. These derived classes only override the methods they need to override.
- The wrapper class maintains a "current" State object.
- All client requests to the wrapper class are simply delegated to the current State object, and the wrapper object's
this
pointer is passed. - The State methods change the "current" state in the wrapper object as appropriate
First of all, we define an abstract interface for state
class State {
public:
virtual void insertQuarter() = 0;
virtual void ejectQuarter() = 0;
virtual void turnCrank() = 0;
virtual void dispense() = 0;
virtual void toString() = 0;
};
Such abstract class does nothing but defines an uniform interface for states via several pure virtual functions. Then we further implement such interface to define concrete states. We look at HasQuarterState
class HasQuarterState : public State {
private:
GumballMachine* gumballMachine;
public:
HasQuarterState(GumballMachine* gumballMachine) {
this->gumballMachine = gumballMachine;
}
virtual void insertQuarter() {
std::cout << "You can't insert another quarter" << std::endl;
}
virtual void ejectQuarter() {
std::cout << "Quarter returned" << std::endl;
gumballMachine->setState(gumballMachine->getNoQuarterState());
}
virtual void turnCrank() {
std::cout << "You turned..." << std::endl;
gumballMachine->setState(gumballMachine->getSoldState());
}
virtual void dispense() {
std::cout << "No gumball dispensed" << std::endl;
}
virtual void toString() {
std::cout << "waiting for turn of crank" << std::endl;
}
};
HasQuarterState inherits State. It has a private data member, which points to our gumballMachine. This is because in the state, during conducting certain move, we may change state of gumballMachine. Because we have quarter in the machine, we cannot insertQuarter(), however, we could ejectQuarter(). Moreover, we could use the quarter to buy gumballs, turnCrank. If you do nothing, it will print "waiting for turn of crank". The other states are
class NoQuarterState : public State {
private:
GumballMachine* gumballMachine;
public:
NoQuarterState(GumballMachine* gumballMachine) {
this->gumballMachine = gumballMachine;
}
virtual void insertQuarter() {
std::cout << "You inserted a quarter" << std::endl;
gumballMachine->setState(gumballMachine->getHasQuarterState());
}
virtual void ejectQuarter() {
std::cout << "You haven't inserted a quarter" << std::endl;
}
virtual void turnCrank() {
std::cout << "You turned, but there's no quarter" << std::endl;
}
virtual void dispense() {
std::cout << "You need to pay first" << std::endl;
}
virtual void toString() {
std::cout << "waiting for quarter" << std:endl;
}
};
class SoldOutState : public State {
private:
GumballMachine* gumballMachine;
public:
SoldOutState(GumballMachine* gumballMachine) {
this->gumballMachine = gumballMachine;
}
virtual void insertQuarter() {
std::cout << "You can't insert a quarter, the machine is sold out" << std::endl;
}
virtual void ejectQuarter() {
std::cout << "You can't eject, you haven't inserted a quarter yet" << std::endl;
}
virtual void turnCrank() {
std::cout << "You turned, but there are no gumballs" << std::endl;
}
virtual void dispense() {
std::cout << "No gumball dispensed" << std::endl;
}
virtual void toString() {
std::cout << "sold out" << std::endl;
}
};
class SoldState : public State {
private:
GumballMachine* gumballMachine;
public:
SoldState(GumballMachine* gumballMachine) {
this->gumballMachine = gumballMachine;
}
virtual void insertQuarter() {
std::cout << "Please wait, we're already giving you a gumball" << std::endl;
}
virtual void ejectQuarter() {
std::cout << "Sorry, you already turned the crank" << std::endl;
}
virtual void turnCrank() {
std::cout << "Turning twice doesn't get you another gumball!" << std::endl;
}
virtual void dispense() {
gumballMachine->releaseBall();
if (gumballMachine->getCount() > 0) {
gumballMachine->setState(gumballMachine->getNoQuarterState());
} else {
std::cout << "Oops, out of gumballs!" << std::endl;
gumballMachine->setState(gumballMachine->getSoldOutState());
}
}
virtual void toString() {
std::cout << "dispensing a gumball" << std::endl;
}
};
The GumballMachine class is given by
#ifndef GUMBALLMACHINE_HPP_INCLUDED
#define GUMBALLMACHINE_HPP_INCLUDED
class GumballMachine {
private:
State* soldOutState;
State* noQuarterState;
State* hasQuarterState;
State* soldState;
State* state; // = soldOutState;
int count; // = 0;
public:
GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
winnerState = new WinnerState(this);
this->state = soldOutState;
this->count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
}
virtual void insertQuarter() {
state->insertQuarter();
}
virtual void ejectQuarter() {
state->ejectQuarter();
}
virtual void turnCrank() {
state->turnCrank();
state->dispense();
}
virtual void setState(State* state) {
this->state = state;
}
virtual void releaseBall() {
std::cout << "A gumball comes rolling out the slot..." << std::endl;
if (count != 0) {
count = count - 1;
}
}
virtual int getCount() { return count; }
irtual void refill(int count) {
this->count = count;
state = noQuarterState;
}
virtual State* getState() { return state; }
virtual State* getSoldOutState() { return soldOutState; }
virtual State* getNoQuarterState() { return noQuarterState; }
virtual State* getHasQuarterState() { return hasQuarterState; }
virtual State* getSoldState() { return soldState; }
virtual void toString() {
std::cout << std::endl << "Mighty Gumball, Inc.";
std::cout << std::endl << "C++-enabled Standing Gumball Model #2004";
std::cout << std::endl << "Inventory: " << count << " gumball";
if (count != 1) {
std::cout << "s";
}
std::cout << std::endl;
std::cout << "Machine is " << state->toString() << std::endl;
}
};
#endif // GUMBALLMACHINE_HPP_INCLUDED
The main function
int main() {
GumballMachine* gumballMachine = new GumballMachine(10);
std::cout << gumballMachine->toString() << std::endl;
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
std::cout << gumballMachine->toString() << std::endl;
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
std::cout << gumballMachine->toString() << std::endl;
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
std::cout << gumballMachine->toString() << std::endl;
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
std::cout << gumballMachine->toString() << std::endl;
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
gumballMachine->insertQuarter();
gumballMachine->turnCrank();
std::cout << gumballMachine->toString() << std::endl;
return 0;
}