书上对该模式的第一解释是:策略模式和状态模式是双胞胎,在出生时才分开。暂时不太明白,我们继续往下学。
场景描述
假设现在要实现一个糖果贩卖机,投一美分硬币,转动曲柄,机器判断是否有糖,有的话吐出来,售罄的话将硬币退还。这个过程会涉及到一系列的状态,比如说:有没有投币、是否已售罄等。下面用实例变量的普通方式来实现这个场景。
创建糖果机
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133public 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) {// 已投25美分,不能再投了System.out.println("You can't insert another quarter");} else if (state == NO_QUARTER) {// 接受25美分,并更改成已经有25美分的状态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) {// 已经转动曲柄,需要稍等,等待状态转换成没有25美分System.out.println("Please wait, we're already giving you a gumball");}}/*** 退币*/public void ejectQuarter() {if (state == HAS_QUARTER) {// 有25美分的话,退出25美分,回到没有25美分状态System.out.println("Quarter returned");state = NO_QUARTER;} else if (state == NO_QUARTER) {// 没有25美分的话,不能退出25美分System.out.println("You haven't inserted a quarter");} else if (state == SOLD_OUT) {// 糖果售罄,不可能接收25美分,当然也不可能退款System.out.println("You can't eject, you haven't inserted a quarter");} else if (state == SOLD) {// 已经转动曲柄,不能退款了System.out.println("Sorry, you already turned the crank");}}/*** 转动曲柄*/public void turnCrank() {if (state == HAS_QUARTER) {// 转动曲柄,将状态改成售出,然后调用dispense()方法System.out.println("You turned...");state = SOLD;dispense();} else if (state == NO_QUARTER) {// 没有25美分的话,转动曲柄无效System.out.println("You turned, but there's no quarter");} else if (state == SOLD_OUT) {// 糖果售罄,转动曲柄无效System.out.println("You turned, there are no gumballs");} else if (state == SOLD) {// 已经转动曲柄,不能再转了System.out.println("Turning twice doesn't get you another gumball");}}/*** 发糖果*/public void dispense() {if (state == HAS_QUARTER) {// 错误状态,得不到糖果System.out.println("No gumball dispensed");} else if (state == NO_QUARTER) {// 还没投币System.out.println("You need pay first");} else if (state == SOLD_OUT) {// 错误状态,得不到糖果System.out.println("No gumball dispensed");} else if (state == SOLD) {// 成功,发出糖果System.out.println("A gumball comes rolling out the slot");count = count - 1;if (count == 0) {// count == 0 售罄System.out.println("0pps, out of gumballs");state = SOLD_OUT;} else {// 未售罄,状态变为等待投币state = NO_QUARTER;}}}public void refill(int numGumBalls) {this.count = numGumBalls;state = NO_QUARTER;}public String toString() {StringBuffer result = new StringBuffer();result.append("\nMighty Gumball, Inc.");result.append("\nJava-enabled Standing Gumball Model #2004\n");result.append("Inventory: " + count + " gumball");if (count != 1) {result.append("s");}result.append("\nMachine is ");if (state == SOLD_OUT) {result.append("sold out");} else if (state == NO_QUARTER) {result.append("waiting for quarter");} else if (state == HAS_QUARTER) {result.append("waiting for turn of crank");} else if (state == SOLD) {result.append("delivering a gumball");}result.append("\n");return result.toString();}}测试
1234567891011121314151617181920212223242526272829303132333435public class Test {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);}}
可以看出用成员变量状态来控制状态的变更,很不友好,这么多if/else
容易出错,当需求有变更时也不容易扩展。比如说下面要新加个需求,当曲柄被转动时,有10%的几率掉下两颗糖果,如果这个需要新增一个状态的话,每个方法都得修改一遍。下面我们来学习使用状态模式重写这个场景。
代码示例
创建状态接口
1234567891011public interface State {public void insertQuarter();public void ejectQuarter();public void turnCrank();public void dispense();public void refill();}创建一个空的糖果机,目的是构造各个状态的时候需要用到该类。这个糖果机存储了一系列实现
State
接口的状态,同时包含它本身该有的行为。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859public class GumballMachine {State hasQuarterState;State noQuarterState;State soldState;State soldOutState;State state = soldOutState;int count = 0;public GumballMachine(int numberGumballs) {this.count = numberGumballs;if (numberGumballs > 0) {state = noQuarterState;}}public void insertQuarter() {}public void ejectQuarter() {}public void turnCrank() {}void setState(State state) {this.state = state;}void releaseBall() {System.out.println("A gumball comes rolling out the slot...");if (count != 0) {count--;}}public State getHasQuarterState() {return hasQuarterState;}public State getNoQuarterState() {return noQuarterState;}public State getSoldState() {return soldState;}public State getSoldOutState() {return soldOutState;}public int getCount() {return count;}}实现
NoQuarter
状态,状态实现中保存了糖果机对象,其中实现的所有方法都是基于在当前状态下允许的行为12345678910111213141516171819202122232425262728293031323334353637383940public class NoQuarterState implements State {GumballMachine gumballMachine;public NoQuarterState(GumballMachine gumballMachine) {this.gumballMachine = gumballMachine;}/*** 当前状态下, 可以投币*/public void insertQuarter() {System.out.println("You inserted 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");}}实现
HasQuarter
状态1234567891011121314151617181920212223242526272829303132333435363738394041public 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");}}实现
Sold
状态123456789101112131415161718192021222324252627282930313233343536373839404142434445public 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());}}}实现
SoldOut
状态123456789101112131415161718192021222324252627282930313233343536373839public 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");}}完善糖果机,将几个状态保存到其中,并完善行为
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778public class GumballMachine {State hasQuarterState;State noQuarterState;State soldState;State soldOutState;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();}void setState(State state) {this.state = state;}void releaseBall() {System.out.println("A gumball comes rolling out the slot...");if (count != 0) {count--;}}public State getHasQuarterState() {return hasQuarterState;}public State getNoQuarterState() {return noQuarterState;}public State getSoldState() {return soldState;}public State getSoldOutState() {return soldOutState;}public int getCount() {return count;}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();}}测试
12345678910111213141516171819public class Test {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();System.out.println(gumballMachine);}}
通过代码可以很清晰地看出,将每个状态从if/else
中独立出来,管好自己当前状态下该做的事情,这样代码清晰,也便于扩展。这个模式跟策略模式确实很像,都是通过组合的方式来改变类的行为,之间的差别在于它们的意图。状态模式是将一群行为封装在状态对象中,可以随时委托到其中的一个。而策略模式的意图是在封装的行为中可以动态地选择最适当的策略去执行。之前说的十次抽中一次的游戏,尚未解决。。。
扩展抽奖功能
场景上面已经说过了,就是有10%地几率掉下两颗糖果,下面我们来扩展代码。
添加一个
WinnerState
状态,该状态和Sold
状态是差不多的,不同的是,该状态下,会掉两颗糖果12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152public class WinnerState implements State {GumballMachine gumballMachine;public WinnerState(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() {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());}}}}修改糖果机,在其中添加
WinnerState
,下面只贴出扩展部分123456789101112public class GumballMachine {......State state = soldOutState;public GumballMachine(int numberGumballs) {winnerState = new WinnerState(this);}public State getWinnerState() {return winnerState;}}修改
HashQuarter
状态,增加随机数,产生10%赢的机会,下面只贴出扩展部分1234567891011121314151617public class HasQuarterState implements State {Random randomWinner = new Random(System.currentTimeMillis());/*** 当前状态下,可以转动曲柄,并且状态变成已出售*/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());}}}测试,多跑几次,将有几率掉落两颗糖果
12345678910111213141516171819public class Test {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);}}
通过修改需求可见扩展还是挺方便的,原有代码的改动也不大,逻辑清晰,不容易出错。我们再回顾下之前容易混淆的集中模式:
- 状态模式:封装基于状态的行为,并将行为委托到当前状态
- 策略模式:将可以互换的行为封装起来,然后使用委托的方法,决定使用哪一个行为
- 模板方法:由子类决定如何实现算法中的某些步骤
状态模式的学习就到这里了。