7.4 状态模式
对于熟知面向对象方法的人来说,每一个条件分支都可以用动态分发来解决(换句话说,都可以用C++里面的虚函数来解决)。但是,如果这样做,你可能会把简单问题复杂化。有时候,一个简单的if语句就足够了。
状态模式的由来也有一些历史原因。许多面向对象设计的拥护者—— GoF和重构的作者Martin Fowler都是Smalltalk出身。在那里,如果有一个ifThen语句,我们便可以用一个表示true和false的对象来操作。
但是,在我们这个例子当中,我们发现面对对象设计也就是状态模式更合适。
GoF描述的状态模式在应用到我们的例子中时如下。
7.4.1 一个状态接口
首先,我们为状态定义一个接口。每一个与状态相关的行为都定义成虚函数。在我们的例子中,就是handleInput()和update()函数。
class HeroineState
{
public:
virtual ~HeroineState() {}
virtual void handleInput(Heroine& heroine,
Input input) {}
virtual void update(Heroine& heroine) {}
};
7.4.2 为每一个状态定义一个类
对于每一个状态,我们定义了一个类并继承此状态接口。它的方法定义主角对应此状态的行为。换句话说,把之前的switch语句里面的每一个case语句里的内容放置到它们对应的状态类里面去。比如:
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_;
};
注意,我们这里chargeTime_从Heroine类中移到了DuckingState(躲避状态)类中。这样非常好,因为这个变量只是对躲避状态有意义,现在把它定义在这里,正好显式地反映了我们的对象模型。
7.4.3 状态委托
接下来,我们在主角类中定义一个指针变量,让它指向当前的状态。我们把之前那个很大的switch语句去掉,并让它去调用状态接口的虚函数,最终这些虚方法就会动态地调用具体子状态的相应函数。
状态委托看起来很像策略模式和类型对象模式(第13章)。在这三个模式中,你会有一个主对象委托给另外的附属对象。它们三者的区别主要在于目的不同:
策略模式的目标是将主类与它的部分行为进行解耦。
类型对象模式的目标是使得多个对象通过共享相同类型对象的引用来表现出相似性。
状态模式的目标是通过改变主对象代理的对象来改变主对象的行为。
class Heroine
{
public:
virtual void handleInput(Input input)
{
state_->handleInput(*this, input);
}
virtual void update() { state_->update(*this); }
// Other methods...
private:
HeroineState* state_;
};
为了修改状态,我们需要把state_指针指向另一个不同的HeroineState状态对象。至此,我们的状态模式就讲完了。