定义
状态模式,允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,行为会随着内部状态而改变。
实例
以糖果机为例,自动售卖糖果。
糖果机工作状态流程:
没有25分钱—》投入25分钱—》有25分钱—》转动曲柄—》售出糖果—》发放糖果,如果此时糖果数目等于0,则变成糖果售罄,否则返回没有25分钱状态。
其中,在投入25分钱,未转动曲柄前,可退回25分钱。
在糖果售罄状态时,不能投25分钱。
很明显,这个例子有4个状态:没有25分钱,有25分钱,售出糖果,糖果售罄。
有4个动作:投入25分钱,退回25分钱,转动曲柄,发放糖果。
常规方法是创建状态常量值
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
int state = SOLD_OUT;
然后在糖果机中,用if条件一个个判断状态并实现具体逻辑。这样的做法会使代码难以维护,当需求变动时,需要改动这一大块的if代码。
我们可以用状态模式优化,将每一个状态都作为一个类,在状态类中实现自己的行为。Context即糖果机,根据行为切换具体的状态类。
以没有25分状态为例,看修改后的实现:
// 实现统一的State接口
public class NoQuarterState impplements State {
GumballMachine gumballMachine;
// 通过构造器得到糖果机的引用
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
// 如果有人投了25分钱,打印消息,并改变机器状态到有25分钱
public void insertQuarter() {
System.out.println("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
// 如果没给钱,就不能要求退钱
public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}
// 如果没给钱,就不能要求糖果
public void turnCrank() {
System.out.println("You turned, but there's no quarter");
}
// 如果没得到钱,我们就不能发放糖果
public void dispense() {
System.out.println("You need to pay first");
}
}
重新改造糖果机:
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state = soldOutState;
int count = 0;
}
其他状态的代码,与没有25分状态类似,都在自己的状态类中实现具体行为逻辑。
与策略模式对比
状态模式与策略模式的类图是一样的,它们的区别在于意图不同。
以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,context的行为也会跟着改变。但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。
而以策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最恰当的策略对象。
一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很难。有了策略模式,你可以通过组合不同的对象来改变行为。
我们把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context行为。
小结
-
状态模式允许一个对象基于内部状态而拥有不同的行为。
-
和程序状态机(PSM)不同,状态模式用类代表状态。
-
Context类会将行为委托给当前状态对象。
-
通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了。
-
状态模式和策略模式有相同的类图,但是它们的意图不同。
-
策略模式通常会用行为或算法来配置Context类。
-
状态模式允许Context随着状态的改变而改变行为。
-
状态转换可以由State类或Context类控制。
-
使用状态模式通常会导致设计中类的数目大量增加。
-
状态类可以被多个Context实例共享。