设计模式之状态模式 糖果机的需求 我们公司接到糖果公司的需求,要设计一个糖果机的程序,糖果机有四个状态,未投币,已投币,售出,售罄这四个状态,当客户投币后,就开始转动曲柄,然后发放糖果。当糖果售罄时,就不在允许投入币了。所以希望我们设计能够尽量有弹性而且好维护的程序,在将来有可能增加更多的行为。
糖果机初版代码实现 现在我们来实现糖果机。我们知道要利用实例变量持有当前的状态,然后需要处理所有可能发生的动作、行为和状态的转换。我们需要实现的工作包括:投币、退币、转动曲柄和发放糖果;也需要检查糖果是否售罄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 public class GumballMachine { public static final int SOLD_OUT = 0 ; public static final int NO_QUARTER = 1 ; public static final int HAS_QUARTER = 2 ; public static final int SOLD = 3 ; private int state = SOLD_OUT; private 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 cant'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 solt." ); count--; 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 + '}' ; } }
测试糖果机 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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); } }
新的需求 糖果公司在程序运行一段时间后,想要新增一个功能,有一个幸运玩家,就是售出糖果的时候,有10%的几率会掉出2课糖果,而不是一颗。
面对原有的程序,我们就必须要新增一个状态,称为“赢家”。还不太麻烦,不过在每个方法的条件判断里处理“赢家”状态;这可有的忙了。随着新的行为出现,这个程序越来越难以适用。
新的设计 我们的计划是:不要维护我们现有的代码,我们重写它以便于将状态对象封装在各自的类中,然后在动作发生时委托给当前状态。
首先,我们定义一个State接口。在这个接口内,糖果机的每个动作都有一个对应的方法。
然后为机器中的每个状态实现状态类。这些类将负责对应的状态下进行机器的行为
最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类。
定义状态模式 状态模式 :允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
实现状态类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public interface State { void insertQuarter () ; void ejectQuarter () ; void turnCrank () ; void dispense () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class NoQuarterState implements State { private final GumballMachine gumballMachine; public NoQuarterState (GumballMachine gumballMachine) { this .gumballMachine = gumballMachine; } @Override public void insertQuarter () { System.out.println("You inserted a quarter." ); gumballMachine.setState(gumballMachine.getHasQuarterState()); } @Override public void ejectQuarter () { System.out.println("You haven't inserted a quarter." ); } @Override public void turnCrank () { System.out.println("You turned, but there's no quarter." ); } @Override public void dispense () { System.out.println("You need to pay first." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class HasQuarterState implements State { private final GumballMachine gumballMachine; private Random random = 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 = random.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." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class SoldOutState implements State { private final 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." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class SoldState implements State { private final 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()); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class WinnerState implements State { private final GumballMachine gumballMachine; public WinnerState (GumballMachine gumballMachine) { this .gumballMachine = gumballMachine; } @Override public void insertQuarter () { } @Override public void ejectQuarter () { } @Override public void turnCrank () { } @Override public void dispense () { System.out.println("YOU'RE A WINNER! You get two gumballs for you 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()); } } } }
改造糖果机 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 public class GumballMachine { private State soldOutState; private State noQuarterState; private State hasQuarterState; private State soldState; private State winnerState; private State state = soldOutState; private int count = 0 ; public GumballMachine (int count) { soldOutState = new SoldOutState (this ); noQuarterState = new NoQuarterState (this ); hasQuarterState = new HasQuarterState (this ); soldState = new SoldState (this ); winnerState = new WinnerState (this ); this .count = count; if (count > 0 ) { state = noQuarterState; } } public void insertQuarter () { state.insertQuarter(); } public void ejectQuarter () { state.ejectQuarter(); } public void turnCrank () { state.turnCrank(); state.dispense(); } public void dispense () { state.dispense(); } public void releaseBall () { System.out.println("A gumball comes rolling out the slot." ); if (count != 0 ) { count--; } } public 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 int getCount () { return count; } public State getWinnerState () { return winnerState; } }
展示新程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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(); System.out.println(gumballMachine); } }
总结
状态模式允许一个对象基于内部状态而拥有不同的行为
和程序状态机不同,状态模式用类代表状态
Context会将行为委托给当前状态对象
通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了
状态模式和策略模式有不同的类图,但是它们的意图不同
策略模式通常会用行为或算法来配置Context类
状态模式允许Context随着状态的改变而改变行为
状态模式可以由State类或Context类控制
使用状态模式通常会导致设计中的类数目大量增加
状态类可以被多个Context实例共享