第十三章 状态模式
1.1 糖果机的状态
在现实世界中,有一台糖果机,可以糖果机上执行如下动作:
- 投入一枚25分钱的硬币(机器上有按钮)
- 退出一枚25分钱的硬币(机器上有按钮)
- 转动曲柄以获取糖果(机器上有曲柄)
- 糖果机释放糖果(糖果机在转动曲柄后,自动释放)
- 重新为糖果机装填糖果
而糖果机的状态转换如下:
假定糖果机中有一定量的糖果,此时为 没有25分钱 的状态,当向糖果机投入25分钱后,糖果机进入有25分钱 的状态,在此状态下,可以按下退出硬币按钮,待退出硬币后,进入 没有25分钱 ,或者转动曲柄,进入售出糖果 的状态,然后机器自动发放糖果,发放糖果后,根据糖果机内的糖果数量选择进入糖果售罄或者是 没有25分钱的状态。
1.2 使用状态模式定义糖果机
定义糖果机的状态和接口:
因为我们可能在任意一种状态下,对糖果机执行动作,因此,状态接口实现了不同动作的接口。
实现糖果机 GumballMachine 的代码:
package headfirst.designpatterns.state.gumballstate;
public class GumballMachine {
// 糖果机中有不同的状态属性,糖果机的当前状态可以在这些不同状态中切换。
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
// 糖果机的当前状态
State state;
// 糖果机中的糖果数量
int count = 0;
// 给定糖果数量,初始化糖果机。
public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
} else {
state = soldOutState;
}
}
// 对糖果机中执行操作时,实际是对当前状态下的糖果机进行动作,因此调用当前状态下的对应动作。
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
// 由于糖果机在转动曲柄后是自动释放糖果,因此,转动曲柄和释放糖果是先后顺序执行。
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count > 0) {
count = count - 1;
}
}
int getCount() {
return count;
}
void refill(int count) {
this.count += count;
System.out.println("The gumball machine was just refilled; its new count is: " + this.count);
state.refill();
}
void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public State getSoldOutState() {
return soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004");
result.append("\nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n");
result.append("Machine is " + state + "\n");
return result.toString();
}
}
定义 State 接口:
package headfirst.designpatterns.state.gumballstate;
public interface State {
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
void refill();
}
定义没有25分钱 的状态:
package headfirst.designpatterns.state.gumballstate;
public class NoQuarterState implements State {
// 在当前状态执行相应的操作后,涉及对糖果机状态的改变,因此,依赖GumballMachine。
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
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 void refill() { }
public String toString() {
return "waiting for quarter";
}
}
定义有25分钱 的状态:
package headfirst.designpatterns.state.gumballstate;
public class HasQuarterState implements State {
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("You can't insert another quarter");
}
public void ejectQuarter() {
System.out.println("Quarter returned");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public void turnCrank() {
System.out.println("You turned...");
gumballMachine.setState(gumballMachine.getSoldState());
}
public void dispense() {
System.out.println("No gumball dispensed");
}
public void refill() { }
public String toString() {
return "waiting for turn of crank";
}
}
定义售出糖果的状态:
package headfirst.designpatterns.state.gumballstate;
public class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("Please wait, we're already giving you a gumball");
}
public void ejectQuarter() {
System.out.println("Sorry, you already turned the crank");
}
public void turnCrank() {
System.out.println("Turning twice doesn't get you another gumball!");
}
public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
public void refill() { }
public String toString() {
return "dispensing a gumball";
}
}
定义售罄糖果的状态:
package headfirst.designpatterns.state.gumballstate;
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("You can't insert a quarter, the machine is sold out");
}
public void ejectQuarter() {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
public void turnCrank() {
System.out.println("You turned, but there are no gumballs");
}
public void dispense() {
System.out.println("No gumball dispensed");
}
public void refill() {
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public String toString() {
return "sold out";
}
}
测试:
package headfirst.designpatterns.state.gumballstate;
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(2);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.refill(5);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
测试结果:
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
Machine is waiting for quarter
You inserted a quarter
You turned...
A gumball comes rolling out the slot...
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 1 gumball
Machine is waiting for quarter
You inserted a quarter
You turned...
A gumball comes rolling out the slot...
Oops, out of gumballs!
You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs
No gumball dispensed
The gumball machine was just refilled; its new count is: 5
You inserted a quarter
You turned...
A gumball comes rolling out the slot...
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter
1.3 状态模式
状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
这里的对象为糖果机,其状态改变时,操作糖果机会导致其有不同的行为。
状态模式将状态封装成独立的类,并将动作委托到代表当前状态的对象,行为随内部状态改变而改变。例如:糖果机在NoQuarterState和HasQuarterState两种状态时,投入 25分钱,会得到不同的行为。
可以忽略对象组合和个别对象之间的差异。
组合模式的类图如下图所示。
状态模式与策略方式很像,状态模式是将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。
参考
[1] Freeman E. Head First 设计模式[M] 中国电力出版社.
[2] 菜鸟教程.