以糖果机为例
我们需要使用java实现一个糖果机,糖果机状态如下所示:
![43259283]()
状态机101
使用状态机的方式实现,这里使用一个通用技巧:
如何对对象的内的状态建模–通过创建一个实例变量来持有状态值,并在方法内书写条件代码来处理不同状态。
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
| public class GumballMachine101 {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
private int state = SOLD_OUT;
private int count = 0; public GumballMachine101(int count){ this.count = count; if (count > 0){ state = NO_QUARTER; } }
public void insertQuarter(){ if (state == HAS_QUARTER){ System.out.println("你不能重复投入硬币"); } else if (state == NO_QUARTER){ state = HAS_QUARTER; System.out.println("投入硬币"); } else if (state == SOLD_OUT){ System.out.println("无法投入硬币,糖果已售罄"); } else if (state == SOLD){ System.out.println("请等待,正在为您准备糖果"); } }
public void ejectQuarter(){ if (state == HAS_QUARTER){ System.out.println("硬币已退回"); state = NO_QUARTER; } else if (state == NO_QUARTER){ System.out.println("无法退回,您还未投入硬币"); } else if (state == SOLD){ System.out.println("无法退回,您已经转动了曲柄"); } else if (state == SOLD_OUT){ System.out.println("无法退回,您还未投入硬币"); } }
public void trunCrank(){ if (state == SOLD) { System.out.println("不能转动2次曲柄"); } else if (state == NO_QUARTER){ System.out.println("还没投入硬币"); } else if (state == SOLD_OUT) { System.out.println("糖果已售罄"); } else if (state == HAS_QUARTER) { System.out.println("转动曲柄。。。"); state = SOLD; dispense(); } }
private void dispense() { if (state == SOLD) { System.out.println("售出糖果"); count--; if (count == 0){ System.out.println("糖果卖完了"); state = SOLD_OUT; } else { state = NO_QUARTER; } } else if (state == NO_QUARTER) { System.out.println("需要先投入硬币"); } else if (state == SOLD_OUT) { System.out.println("没有糖果可发放"); } else if (state == HAS_QUARTER) { System.out.println("没有糖果可发放"); } } }
|
变更请求
我们需要在糖果机上变点花样,当曲柄转动时,有10%的概率掉下来的是两颗糖果。
使用一种考虑周详的方法写糖果机的代码,并不意味着这份代码容易扩展。首先,你必须加上一个新的状态,“赢家”状态。然后,你必须在每个方法中加入一个新的条件判断来处理“赢家”状态,这可就有忙的了。
trunCrack()尤其会变得一团乱,因为你必须加入代码来检查目前的顾客是否是赢家,然后再决定切换到赢家状态还是售出糖果转态。
新的设计
不要维护现有代码,我们重写它以便于将状态对象封装在各自的类中,然后在动作发生时委托给当前状态。
- 首先,我们定义一个state接口。在这个接口中,糖果机的每个动作都有一个对应的方法。
- 然后为机器中的每个状态实现状态类。这些类将负责在对应的状态下进行机器的行为。
- 最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类。
![47618146]()
糖果机:
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
| @Data public class GumballMachine {
private State soldOutState;
private State noQuarterState;
private State hasQuarterState;
private State soldState;
private State state = soldOutState;
private 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(); } }
|
状态对象NoQuarterState:
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
| public class NoQuarterState implements State { private GumballMachine gumballMachine; public NoQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; }
@Override public void insertQuarter() { System.out.println("投入硬币"); gumballMachine.setState(gumballMachine.getHasQuarterState()); }
@Override public void ejectQuarter() { System.out.println("无法退回,您还未投入硬币"); }
@Override public void turnCrank() { System.out.println("请先投入硬币"); }
@Override public void dispense() { System.out.println("请先投入硬币"); } }
|
到目前为止,我们做了哪些事情?
- 将每个状态的行为局部化到它自己的类中。
- 将容易产生的问题的if语句删除,以方便日后维护。
- 让每一个状态“对修改关闭”,让糖果机“对扩展开放”。
![76555373]()
定义状态模式
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
因为这个类将状态封装成独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态的改变而改变。
从客户的视角来看,如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。
![77253919]()
从类图上看,策略模式和状态模式的类图是一样的,但是这两个模式的差别在于它们的“意图”。
以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。当前状态在状态对象集合中游走改变,以反映出context内部状态,因此context的行为也会跟着改变。
以策略模式而言,客户通常主动指定context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最合适的策略对象。
一般来说,我们把策略模式想成是除了继承之外的一种弹性替代。
把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context中简单的改变状态对象来改变context的行为。
源码:https://github.com/chentianming11/design-pattern
state包!