策略模式和状态模式很相似,状态模式通过更改对象内部的状态帮助对象控制自己的行为。
案例
现在需要一个糖果机,智能糖果机,分别由几个状态控制糖果机,未投钱、已投入、正常售卖和售罄状态,我们也有四个动作,分别是投入钱、退回钱、转动曲柄和发放糖果。很清晰明了的类结构,我们现在来看一下。
public class GumballMachine {
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;
int count = 0;
public GumballMachine(int count) {
this.count = count;
if (count > 0) {
state = NO_QUARTER;
}
}
public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("You can't insert another quarter");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("You inserted a quarter");
} else if (state == SOLD_OUT) {
System.out.println("You can't insert a quarter, the machine is sold out");
} else if (state == SOLD) {
System.out.println("Please wait, we're already giving you a gumball");
}
}
public void ejectQuarter() {
if (state == HAS_QUARTER) {
System.out.println("Quarter returned");
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
System.out.println("You haven't inserted a quarter");
} else if (state == SOLD) {
System.out.println("Sorry, you already turned the crank");
} else if (state == SOLD_OUT) {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
}
public void turnCrank() {
if (state == SOLD) {
System.out.println("Turning twice doesn't get you another gumball!");
} else if (state == NO_QUARTER) {
System.out.println("You turned but there's no quarter");
} else if (state == SOLD_OUT) {
System.out.println("You turned, but there are no gumballs");
} else if (state == HAS_QUARTER) {
System.out.println("You turned...");
state = SOLD;
dispense();
}
}
public void dispense() {
if (state == SOLD) {
System.out.println("A gumball comes rolling out the slot");
count -= 1;
if (count == 0) {
System.out.println("Oops, out of gumballs!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("You need to pay first");
} else if (state == SOLD_OUT) {
System.out.println("No gumball dispensed");
} else if (state == HAS_QUARTER) {
System.out.println("No gumball dispensed");
}
}
@Override
public String toString() {
return "GumballMachine{" +
"state=" + state +
", count=" + count +
'}';
}
}
现在来看一下测试类。
public class gumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.ejectQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.ejectQuarter();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
现在我们仔细来看,SOLD_OUT 为糖果售罄状态,NO_QUARTER 未投币,HAS_QUARTER 已投币,SOLD 正常售卖状态,我们也来看看动作,insertQuarter() 投钱,ejectQuarter() 退钱,turnCrank() 转动曲柄,dispense() 发放糖果,这几个方法都会通过 if-else-if 语句根据状态的不同做不同的操作,可能会更改糖果机的状态。
现在需求有变更,投资方认为将购买糖果变成一个游戏,可以大大增加我们的销售量,所以现在我们就要把这个游戏实现:当曲柄转动时,有 10% 的几率掉下来的是两颗糖果(赠送一颗)。
但是我们要继续沿用上一个版本的代码可能会增大bug的几率,所以我们需要重构这份代码,我们把每个状态的行为都放在各自的类中,这样每个状态只需要实现自己的动作即可,糖果机委托给代表当前状态的状态对象。
我们先设计一个 State 接口,这个接口内糖果机的每个动作都有一个对应的方法。然后为机器中的每个状态实现状态类。这些类负责在对应的状态下进行机器的行为。然后将动作委托到状态类。
我们先来定义状态接口,所有的状态都必须实现这个接口,这些方法直接映射到糖果机上可能发生的动作。
public interface State {
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
接下来修改糖果机类,把状态都换成 State 接口的实现类,共 4 个。count 实例变量记录机器内装有多少糖果,state 实时记录机器的状态。接下来的动作只要委托给当前状态的方法就可以实现了。
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state = soldOutState;
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;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
public void setState(State state) {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count -= 1;
}
}
public State getSoldOutState() {
return soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public State getState() {
return state;
}
public int getCount() {
return count;
}
public void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count -= 1;
}
}
}
下面来看各个状态的代码。
public class HasQuarterState implements State {
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("You can't insert another quarter");
}
@Override
public void ejectQuarter() {
System.out.println("Quarter returned");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("You turned...");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void dispense() {
System.out.println("No gumball dispensed");
}
}
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("You inserted a quarter");
}
@Override
public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}
@Override
public void turnCrank() {
System.out.println("You need to pay first");
}
@Override
public void dispense() {
System.out.println("You need to pay first");
}
}
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("You can't insert a quarter, the machine is sold out");
}
@Override
public void ejectQuarter() {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
@Override
public void turnCrank() {
System.out.println("You turned, but there are no gumballs");
}
@Override
public void dispense() {
System.out.println("No gumball dispensed");
}
}
public class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("Please wait, we're already giving you a gumball");
}
@Override
public void ejectQuarter() {
System.out.println("Sorry, you already turned the crank");
}
@Override
public void turnCrank() {
System.out.println("Turning twice doesn't get you another gumball!");
}
@Override
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());
}
}
}
这四个状态方法和之前的 if-else 判断基本一致,值得注意的是,我们在 SoldState 中的 disppense() 方法,需要调用糖果机释放糖果的方法,然后再根据糖果机内糖果的数量来修改糖果机的状态。
这些工作做完,我们就完成了对糖果机代码的重构,我们把每个状态的行为局部化到自己的类中。还删去了容易产生问题的 if 语句,方便代码的维护。让每一个状态对修改关闭,让糖果机对扩展开放,因为可以加入新的状态类,改变它的状态。到这里先暂停一下,来看看状态模式的定义。
状态模式定义
状态模式允许对象再内部状态改变时改变它的行为,对象看起来好像修改了它的类。
把状态封装成类,并把动作委托到代表当前状态的对象,当有不同的状态时,动作便会随之改变。后半句是从客户的角度来看的,这就是通过使用类的组合引用不同的状态对象造成了类被改变了的假象。
现在我们继续补全我们的代码,还有十次中一次出两颗糖的小游戏还没解决。我们现在已经实现了状态模式的框架,现在我们做这个游戏的代码很简单,只需要增加一个状态就可以实现,并修改部分代码。先来看看新增的 WinnerState 类。
public class WinnerState implements State {
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("Please wait, we're already giving you a Gumball");
}
@Override
public void ejectQuarter() {
System.out.println("Please wait, we're already giving you a Gumball");
}
@Override
public void turnCrank() {
System.out.println("Turning again doesn't get you another gumball!");
}
@Override
public void dispense() {
System.out.println("YOU'RE A WINNER! You get two gumballs for your quarter");
gumballMachine.releaseBall();
if (gumballMachine.getCount() == 0) {
gumballMachine.setState(gumballMachine.getSoldOutState());
} else {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
}
也是包含一个实例变量,一个构造器,其他的方法抛出错误,在最后的 dispense() 方法中我们释放一个糖果,判断一次,如果还有糖果的化,继续释放糖果,再设置相应的状态即可。
现在我们修改随机出现这个情况的代码,需要在 HasQuarterState 类中进行修改。
public class HasQuarterState implements State {
GumballMachine gumballMachine;
Random randomWinner = new Random(System.currentTimeMillis());
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("You can't insert another quarter");
}
@Override
public void ejectQuarter() {
System.out.println("Quarter returned");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("You turned...");
int winner = randomWinner.nextInt(10);
if ((winner == 0) && (gumballMachine.getCount() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState());
} else {
gumballMachine.setState(gumballMachine.getSoldState());
}
}
@Override
public void dispense() {
System.out.println("No gumball dispensed");
}
}
增加一个随机数,10 以内随机,设定当它为 0 且糖果数目大于 1 时进行奖励糖果的发放,否则只发放消费者的一枚糖果,这样我们的游戏就已经实现了。
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
到这里,状态模式写差不多了,这个系统还可以继续改进,比如我们在售出糖果和赢家状态中有许多重复的代码,所以就需要清理一下,我们可以将 State 接口设计成抽象类,把方法的默认行为放在其中,所有的「错误响应」行为都可以写的具有通用性,并放在抽象类中供子类继承,所以就减少了不必要的代码,方便维护。