Some of this explanation I will paraphrase from my copy
Design Patters, by the GoF(group of four) ... (isbn: 9780201633610, it's an excellent read). I will mark those bits by putting them italics.
"The state pattern allows an object to alter its behavior when its internal state changes." So, there will still be the need for some conditional control statements (behavior). But, with the state pattern, that behavior is encapsulated within a logical set of related objects.
This makes the object appear to have changed class when, in reality, it is merely operating under different set of instructions. In other words, instead of defining an object's behavior within its class, you define its behavior in a set of classes (states).
The state pattern,
"localizes state-specific behavior and partitions behavior for different states." With the behavior living within states, you can easily define new states and transitions simply by defining new subclasses. This is much easier than adding new behavior to a lengthy nested if/then clause.
"It makes state transitions explicit." When an object defines its state solely in terms of internal data values, its state transitions are only realized by assignment to those variables. For example, the transition of player object from its walking state to its running state would only be known by the value of its 'speed' variable. Now, say you wanted to add two different types of jump behavior; the first, shortJump, is invoked by pressing the jump key from the walking state; the second, longJump, is invoked with jump key from the running state.
Without the state pattern you would have to check the 'speed' variable every time the jump key is pressed. But this isn't necessary with states because the transition to the jump state is more explicit. With the state pattern the jump key would simply invoke whichever jump behavior is defined by the current state.
"State objects can be shared." If a State object has no instance variables--if it only uses data provided by some Context to which the State object is applied--then it can be shared. For example, the walk, run, shortJump, and longJump states I mentioned can each be shared by all player types or perhaps even all objects of an enemy class. This can be done because the state objects define only behavior: which animation to play, how far to move, and in which direction are all determined by data contained within a context specific to the object that invoked the state.
Implementation:
* Context
-
Defines the methods/data of interest to clients. Continuing from my example, the Context would define GetAnimation() which retrieves the specified animation object from the player object's model; GetPosition() which retrieves the current location of the player object; and GetVelocity() which retrieves the current direction and speed of the player object.
-
Maintains an instance of a ConcreteState subclass that defines the current state.
* State
-
Defines an interface for encapsulating the behavior associated with a particular state of the Context. This in an abstract class.
* ConcreteState subclasses
-
Each subclass implements a behavior associated with a state of the Context. Continuing from my example, one of the following concrete State subclasses: Walking, Running, JumpimgShort, or JumpingLong.
.... and now for some code ....
PlayerContext:
//----------- PlayerContext.h ------------//
class Context
{
public:
PlayerContext(PlayerObject* player);
~PlayerContext(void);
private:
friend class ObjectState;
public:
void inactive(void);
void active(void);
void enter(void);
void leave(void);
void changeState(ObjectState*);
Animation* getAnimation(string const& name);
void setVelocity(Vector3 const& velocity);
Vector3* getVelocity(void);
private:
PlayerObject* player_;
PlayerState* state_;
}; // class Context
//---------- PlayerContext.cpp -----------//
PlayerContext::PlayerContext(PlayerObject* player) : player_(player)
{
state_ = DefaultState::Instance();
}
PlayerContext::~PlayerContext(void)
{
}
void PlayerContext::changeState(PlayerState* state)
{
if (state)
{
state_->leave(this); // invoke behavior for leaving the old state
state_ = state; // switch to the new state
state_->enter(this); // invoke behavior for entering the new state
}
}
// invoke inactive behavior (idle)
void PlayerContext::inactive(void)
{
state_->inactive(this);
}
// invoke active behavior
void PlayerContext::active(void)
{
state_->active(this);
}
// invoke behavior for transitioning to a state (entering state)
void PlayerContext::enter(void)
{
state_->enter(this);
}
// invoke behavior for transitioning from a state (exiting state)
void PlayerContext::leave(void)
{
state_->leave(this);
}
// returns a pointer to an Animation object (used to control the associated PlayerObject's animation)
Animation* PlayerContext::getAnimation(string const& name)
{
return player_->getAnimation(name);
}
// returns a pointer to a Vector3 object containing a player object's position
Vector3* PlayerContext::setVelocity(Vector3 const& velocity)
{
player_->setVelocity(velocity);
}
// returns a pointer to a Vector3 object containing a player object's velocity
Vector3* PlayerContext::getVelocity(void)
{
return player_->getVelocity();
}
PlayerState:
class PlayerState
{
public:
virtual void inactive(PlayerContext*);
virtual void active(PlayerContext*);
virtual void enter(PlayerContext*);
virtual void leave(PlayerContext*);
void printInfo(void);
protected:
void changeState(PlayerContext*, PlayerState*);
protected:
char info_[128];
}; // class PlayerState
Concrete PlayerStates:
//-------------- Header ---------------//
// Default state is concrete PlayerState subclass that simple does nothing.
class DefaultState : public PlayerState
{
public:
static PlayerState* Instance();
static PlayerState* s_instance;
public:
virtual void inactive(PlayerContext* context) { }
virtual void active(PlayerContext* context) { }
virtual void enter(PlayerContext* context) { }
virtual void leave(PlayerContext* context) { }
protected:
void changeState(PlayerContext*, PlayerState*);
}; // class DefaultState
// Concrete PlayerState subclass that defines walking behavior
class Walking : public PlayerState
{
public:
static PlayerState* Instance();
static PlayerState* s_instance;
public:
virtual void inactive(PlayerContext* context) { }
virtual void active(PlayerContext* context) { }
virtual void enter(PlayerContext* context) { }
virtual void leave(PlayerContext* context) { }
protected:
void changeState(PlayerContext*, PlayerState*);
}; // class DefaultState
// Concrete PlayerState subclass that defines running behavior
class Running : public PlayerState
{
public:
static PlayerState* Instance();
static PlayerState* s_instance;
public:
virtual void inactive(PlayerContext* context) { }
virtual void active(PlayerContext* context) { }
virtual void enter(PlayerContext* context) { }
virtual void leave(PlayerContext* context) { }
protected:
void changeState(PlayerContext*, PlayerState*);
}; // class Running
// JumpingShort and JumpingLong are defined similarly.
//-------------- Source ---------------//
// Define default methods common to all subclasses
void PlayerState::changeState(PlayerContext* context, PlayerState* state)
{
context->changeState(state);
}
void PlayerState::printInfo()
{
dbCenterText(dbScreenWidth() / 2, dbTextSize() + 2, info_);
}
// Define Singleton object and Instance method for DefaultState
PlayerState* DefaultState::s_instance = NULL;
PlayerState* DefaultState::Instance()
{
if (DefaultState::s_instance == NULL)
{
s_instance = new DefaultState();
}
return s_instance;
}
// Define Walking behavior
PlayerState* Walking::s_instance = NULL;
PlayerState* Walking::Instance()
{
if (Walking::s_instance == NULL)
{
s_instance = new Walking();
strcpy(static_cast<Walking*>(s_instance)->info_, "Walking State");
}
return s_instance;
}
void Walking::enter(PlayerContext* context)
{
Animation* anim = context->getAnimation("walking");
anim->play();
}
void Walking::active(PlayerContext* context)
{
// check for jump key
if (InputManager::Instance().isDown(KEYID_JUMP))
{
context->changeState(JumpingShort::Instance());
}
Vector3* velocity = context->getVelocity();
// make corresponding velocity adjustments
context->setVelocity(*velocity);
}
void Walking::leave(PlayerContext* context)
{
Animation* anim = context->getAnimation("walking");
anim->stop();
}
// Define Running behavior
PlayerState* Running::s_instance = NULL;
PlayerState* Running::Instance()
{
if (Running::s_instance == NULL)
{
s_instance = new Running();
strcpy(static_cast<Running*>(s_instance)->info_, "Running State");
}
return s_instance;
}
void Running::enter(PlayerContext* context)
{
Animation* anim = context->getAnimation("running");
anim->play();
}
void Running::active(PlayerContext* context)
{
// check for jump key
if (InputManager::Instance().isDown(KEYID_JUMP))
{
context->changeState(JumpingLong::Instance());
}
Vector3* velocity = context->getVelocity();
// make corresponding velocity adjustments
context->setVelocity(*velocity);
}
void Running::leave(PlayerContext* context)
{
Animation* anim = context->getAnimation("running");
anim->stop();
}
// JumpingShort and JumpingLong are similarly defined
...
(and now, I must apologize, I have spent 2hrs on this post and I'm tired ... so I will have to leave further explanations of the above code for next time) ...
Cheers,
~Andrew~