状态模式(State)
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
栗子
现在有一台糖果机,它的状态(挺复杂的):
- 没有 25 分钱 -> 投入 25 分钱 -> 有 25 分钱
- 有 25 分钱 -> 转动曲柄 -> 售出糖果(数量不为0) | 糖果售罄(数量为0)
- 有 25 分钱 -> 退钱按钮 -> 退出 25 分钱
- 售出糖果 -> 没有 25 分钱
从上面的状态实现代码的步骤:
- 找出所有状态 -> 共四种:没有 25 分钱、有 25 分钱、售出糖果、糖果售罄。
- 创建一个持有当前状态的实例变量 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 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
| class GumballMachine { private final static int SOLD_OUT = 0; private final static int NO_QUARTER = 1; private final static int HAS_QUARTER = 2; private final static 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("你已经投过25分钱了,请不要重复投币"); } else if (state == NO_QUARTER) { System.out.println("投进25分钱"); state = HAS_QUARTER; } 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("已退还25分钱"); state = NO_QUARTER; } else if (state == NO_QUARTER) { System.out.println("你还没投币呢"); } else if (state == SOLD_OUT) { System.out.println("没有糖果时无法投币,不要骗钱"); } else if (state == SOLD) { System.out.println("你已经转动曲柄了,无法退钱"); } }
public void turnCrank() { if (state == HAS_QUARTER) { System.out.println("你转动了曲柄"); state = SOLD; dispense(); } else if (state == NO_QUARTER) { System.out.println("你还没投币呢"); } else if (state == SOLD_OUT) { System.out.println("已经没有糖果了"); } else if (state == SOLD) { System.out.println("转几次都只能拿一次"); } }
public 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 == SOLD) { System.out.println("没有糖果发"); } } }
|
多么缜密的判断,基本上没什么漏洞了。
新需求
现在产品经理提需求来了:当曲柄被转动时,有 10% 的几率掉出两个糖果。
看回上面缜密的代码,是不是有点无从入手?这个需求真是要了命了……如果需求再有变更,上面的代码基本上就得推倒重来了,因为代码逻辑判断太复杂。
满足需求
我们可以把这四个状态当作一个实体,比如糖果机当前处在没投币的状态,那么在该状态下,可以通过投币动作使糖果机转移到投了币状态,其他状态也是这样,基本上可以把 if else 这些判断语句分离出来。
定义状态接口:
1 2 3 4 5 6 7 8 9
| interface State { void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense(); }
|
然后每种状态都实现该接口(先看总体):
1 2 3 4 5 6 7 8 9 10
| class SoldState implements State
class SoldOutState implements State
class NoQuarterState implements State
class HasQuarterState implements State
class WinnerState implements 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 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
| class GumballMachine { private State soldState; private State soldOutState; private State noQuarterState; private State hasQuarterState;
private State state = soldState; private int count = 0;
public GumballMachine(int count) { soldState = new SoldState(this); soldOutState = new SoldOutState(this); noQuarterState = new NoQuarterState(this); hasQuarterState = new HasQuarterState(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 setState(State state) { this.state = state; }
public void releaseBall() { System.out.println("糖果已发出"); if (count != 0) { count--; } }
public State getState() { return state; }
public int getCount() { return count; }
public State getSoldState() { return soldState; }
public State getSoldOutState() { return soldOutState; }
public State getNoQuarterState() { return noQuarterState; }
public State getHasQuarterState() { return hasQuarterState; } }
|
分别实现的状态类
没投币的状态:
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
| class NoQuarterState implements State { private GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; }
@Override public void insertQuarter() { System.out.println("投进25分钱"); 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("你要先投币"); } }
|
投了币的状态:
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
| class HasQuarterState implements State { private GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; }
@Override public void insertQuarter() { System.out.println("你已经投过25分钱了,请不要重复投币"); }
@Override public void ejectQuarter() { System.out.println("已退还25分钱"); gumballMachine.setState(gumballMachine.getNoQuarterState()); }
@Override public void turnCrank() { System.out.println("你转动了曲柄"); gumballMachine.setState(gumballMachine.getSoldState()); }
@Override public void dispense() { System.out.println("没有糖果发"); } }
|
售出糖果的状态:
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
| class SoldState implements State { private GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; }
@Override public void insertQuarter() { System.out.println("请稍等,正在出糖果"); }
@Override public void ejectQuarter() { System.out.println("你已经转动曲柄了,无法退钱"); }
@Override public void turnCrank() { System.out.println("转几次都只能拿一次"); }
@Override public void dispense() { gumballMachine.releaseBall(); if (gumballMachine.getCount() > 0) { gumballMachine.setState(gumballMachine.getNoQuarterState()); } else { System.out.println("已经没有糖果喽"); 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
| class SoldOutState implements State { private GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; }
@Override public void insertQuarter() { System.out.println("没有糖果了,不要投币"); }
@Override public void ejectQuarter() { System.out.println("没有糖果时无法投币,不要骗钱"); }
@Override public void turnCrank() { System.out.println("你转动了曲柄,但是已经没有糖果了"); }
@Override public void dispense() { System.out.println("没有糖果发"); } }
|
新需求中的中奖状态:
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
| class WinnerState implements State { private GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; }
@Override public void insertQuarter() { System.out.println("请稍等,正在出糖果"); }
@Override public void ejectQuarter() { System.out.println("你已经转动曲柄了,无法退钱"); }
@Override public void turnCrank() { System.out.println("转几次都只能拿一次"); }
@Override public void dispense() { System.out.println("恭喜!你拿到了两颗糖"); 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("已经没有糖果喽"); gumballMachine.setState(gumballMachine.getSoldOutState()); } } } }
|
需求还没解决完
上面的代码只是解决了状态的问题,但还没有完全满足需求,10% 的随机中奖还没写。(其实也没什么难度了)
给糖果机加上中奖状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class GumballMachine { private State winnerState;
public GumballMachine(String location, int count) { winnerState = new WinnerState(this); }
public State getWinnerState() { return winnerState; }
}
|
因为只有在投了币的情况下才能转动曲柄 -> 发糖 | 中奖,所以要在投了币的状态加入这 10% 随机:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class HasQuarterState implements State {
@Override public void turnCrank() { System.out.println("你转动了曲柄"); Random randomWinner = new Random(System.currentTimeMillis()); int winner = randomWinner.nextInt(10); if (winner == 0 && gumballMachine.getCount() > 1) { gumballMachine.setState(gumballMachine.getWinnerState()); } else { gumballMachine.setState(gumballMachine.getSoldState()); } }
|