状态模式
定义:
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
允许一个对象在其内部状态改变时改变它的行为,看起来就像修改了它的类
类似魂斗罗的游戏中,我们操纵的英雄会有不同的状态,而在不同的状态下,对应不同的输入会有不同的行为。例如在平地上我们按下B按钮英雄就会跳跃,而在高空中我们按B按钮就不会跳跃。(老版魂斗罗高空中按下pause键会清除跳跃状态,所以可以在高空进行跳跃,这是一个我们不想也是不合理的Bug)
1.有限自动机简单实现
首先我们勾画出一幅状态转图:
代码实现:
enum State //表示状态的枚举类
{
STATE_STANDING,
STATE_JUMPING,
STATE_DUCKING,
STATE_DIVING
};
void Heroine::handleInput(Input input) //英雄针对不同输入在不同状态下的行为
{
switch (state_)
{
case STATE_STANDING:
if (input == PRESS_B)
{
state_ = STATE_JUMPING;
yVelocity_ = JUMP_VELOCITY;
setGraphics(IMAGE_JUMP);
}
else if (input == PRESS_DOWN)
{
state_ = STATE_DUCKING;
setGraphics(IMAGE_DUCK);
}
break;
case STATE_JUMPING:
if (input == PRESS_DOWN)
{
state_ = STATE_DIVING;
setGraphics(IMAGE_DIVE);
}
break;
case STATE_DUCKING:
if (input == RELEASE_DOWN)
{
state_ = STATE_STANDING;
setGraphics(IMAGE_STAND);
}
break;
}
}
相比一系列的if,else语句,这种实现已经有了很大的改进,总体逻辑变得清晰。
2.状态类实现
class HeroineState //状态基类
{
public:
virtual ~HeroineState() {}
virtual void handleInput(Heroine& heroine, Input input) {}
virtual void update(Heroine& heroine) {}
};
class DuckingState : public HeroineState //具体状态类
{
public:
DuckingState()
: chargeTime_(0)
{}
virtual void handleInput(Heroine& heroine, Input input) {
if (input == RELEASE_DOWN)
{
// Change to standing state...
heroine.setGraphics(IMAGE_STAND);
}
}
virtual void update(Heroine& heroine) {
chargeTime_++;
if (chargeTime_ > MAX_CHARGE)
{
heroine.superBomb();
}
}
private:
int chargeTime_;
};
class Heroine
{
public:
virtual void handleInput(Input input)
{
state_->handleInput(*this, input);
}
virtual void update()
{
state_->update(*this);
}
// Other methods...
private:
HeroineState* state_; //通过指针指向不同的状态
};
由于状态是影响的一个成员指针,所以不同的状态我们需要存放在一个地方。
2.1静态存放:
class HeroineState
{
public:
static StandingState standing;
static DuckingState ducking;
static JumpingState jumping;
static DivingState diving;
// Other code...
};
if (input == PRESS_B)
{
heroine.state_ = &HeroineState::jumping;
heroine.setGraphics(IMAGE_JUMP);
}
若状态中没有针对英雄对象实体的信息记录,这是一种好的选择。
2.2实例创建
void Heroine::handleInput(Input input)
{
HeroineState* state = state_->handleInput(*this, input);
if (state != NULL)
{
delete state_; //先释放先前的状态
state_ = state;
}
}
HeroineState* StandingState::handleInput(Heroine& heroine,
Input input)
{
if (input == PRESS_DOWN)
{
// Other code...
return new DuckingState();
}
// Stay in this state.
return NULL;
}
这种方式使得每个英雄实例拥有自己的状态,状态实例中可以存放相应的信息,但这种频繁创建释放对于CPU来说本身就是一种消耗。
2.3为状态类添加entry和exit方法
class StandingState : public HeroineState
{
public:
virtual void enter(Heroine& heroine)
{
heroine.setGraphics(IMAGE_STAND);
}
// Other code...
};
void Heroine::handleInput(Input input)
{
HeroineState* state = state_->handleInput(*this, input);
if (state != NULL)
{
delete state_;
state_ = state;
// Call the enter action on the new state.
state_->enter(*this);
}
}
这样我们可以方便的在进入和退出状态时,进行一些信息的处理。
2.4并发状态
当英雄携带不同的武器时,我们的各种行为动作将会有并行的存在,例如跳跃的时候进行射击等。此时我们可以添加一个装备转换状态,使得人物可以完成不同状态的组合。
class Heroine
{
// Other code...
private:
HeroineState* state_;
HeroineState* equipment_; //添加装备状态
};
void Heroine::handleInput(Input input)
{
state_->handleInput(*this, input);
equipment_->handleInput(*this, input); //装备状态的处理
}
3.下推自动机
我们可以通过下推自动机进行状态的保存,这样在进行状态转换处理后,我们可以方便的返回到先前的状态,即用栈实现。
- 栈顶表示当前的状态,我们可以执行push将新的状态压入栈中,而之前的状态将被保留到栈中,不丢弃。
- 我们执行pop丢弃当前的状态,此时将回到之前的状态
状态模式的优点
- 封装了转换规则。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
状态模式的缺点
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。