状态模式
我们用饮料贩卖机工作的例子来讲解状态模式的实现。把贩卖机的工作流程分解,可以分为一般为 待售 -> 选择饮料 -> 插入硬币 -> 售出 -> 退出饮料 -> 回到待售状态。
每一次售出饮料都是这个步骤,贩卖机的状态始终在这些状态中游走。同时为了我们的贩卖机更加的安全,需要在每次请求的时候判断当前的状态是否允许这么做。比如贩卖机要退出饮料,
要先确认当前是否为售出的状态?以及饮料的库存
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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
@Data public class DrinkMachine {
private static final int SOLD_OUT = 0;
private static final int NO_COIN = 1;
private static final int CHOOSE_DRINK = 2;
private static final int HAS_COIN = 3;
private static final int SOLD = 4;
int count = 0;
int currentState = SOLD_OUT;
public DrinkMachine(int count) { this.count = count; if (count > 0) { currentState = NO_COIN; } }
public void chooseDrink() { if (currentState == NO_COIN) { currentState = CHOOSE_DRINK; System.out.println("饮料选择成功,请投币!"); } else if (currentState == SOLD_OUT) { System.out.println("饮料已售罄!"); } else if (currentState == CHOOSE_DRINK) { System.out.println("请勿重复选择!"); } else if (currentState == HAS_COIN) { System.out.println("请勿重复选择!"); } else if (currentState == SOLD) { System.out.println("请勿重复选择!"); } }
public void insertCoin() { if (currentState == CHOOSE_DRINK) { currentState = HAS_COIN; System.out.println("投币成功,请稍等!"); } else if (currentState == SOLD_OUT) { System.out.println("投币失败,饮料已售罄"); } else if (currentState == NO_COIN) { System.out.println("请先选择饮料!"); } else if (currentState == HAS_COIN) { System.out.println("请勿重复投币!"); } else if (currentState == SOLD) { System.out.println("请勿重复投币!"); } }
public void ejectCoin() { if (currentState == HAS_COIN) { currentState = NO_COIN; System.out.println("退币成功!"); } else if (currentState == SOLD_OUT) { System.out.println("退币失败!"); } else if (currentState == CHOOSE_DRINK) { System.out.println("退币失败!"); } else if (currentState == NO_COIN) { System.out.println("请先投币!"); } else if (currentState == SOLD) { System.out.println("退币失败!"); } }
public void returnDrink() { if (currentState == HAS_COIN) { currentState = SOLD; System.out.println("请收好饮料!"); dispense(); } else if (currentState == SOLD_OUT) { System.out.println("饮料已售罄!"); } else if (currentState == CHOOSE_DRINK) { System.out.println("请勿重复选择!"); } else if (currentState == NO_COIN) { System.out.println("请先投币!"); } else if (currentState == SOLD) { System.out.println("饮料已退出!"); } }
public void dispense() { if (currentState == SOLD) { count -= 1; System.out.println("欢迎下次光临!"); if (count >= 1) { currentState = NO_COIN; } else { currentState = SOLD_OUT; } } else if (currentState == NO_COIN) { System.out.println("请先选择饮料!"); } else if (currentState == CHOOSE_DRINK) { System.out.println("请先投币!"); } else if (currentState == HAS_COIN) { System.out.println("请点击退出饮料!"); } else if (currentState == SOLD_OUT) { System.out.println("饮料已售罄!"); } }
}
|
上面是根据贩卖机的工作流程编写的代码。首先贩卖机类内部有几种状态常量,还有记录当前状态和当前的数量的变量。然后是几个操作方法分别为:选择饮料
,投币
,退币
,机器退出饮料
,发放饮料
。
选择饮料
只有在待售
的状态才能选择饮料,否则提示错误信息。选择饮料后则将状态转为选择饮料的状态
等待投入硬币
投币
只有在选择饮料状态
下才能够投币,投币后变为已投币
状态等待用户确认购买操作
退币
在已投币
状态下用户可以选择退币,退币后状态转为未投币
确认购买饮料
在已投币
状态下除了可以退币,还可以确认购买,确认购买后饮料机将状态转为SOLD
状态并调用dispense()
方法,执行发放饮料操作
- 发放饮料
将count - 1并判断剩余数量,转为售罄或未投币
上面的实现代码满足了贩卖机的需求,但是并不是健壮的代码。首先违反了开-闭原则,当有新的状态加入我们就不得不重新打开代码这个类来修改,并且每个方法都会被影响。。。
而且代码可读性也不佳。if-else是很影响代码可读性的,state的变化也隐藏在if语句里,并不明显。很可能对后面维护这些代码的人带来麻烦。重要的是没有将变化的部分 封装 起来!
引入状态模式
现在我们尝试将“变化的部分”封装起来,变化的部分就是 状态。利用面向对象的思想,将每个状态封装为一个具体的类,将状态各自的行为放到自己的类中,那么每个状态只要实现自己的规则即可。
然后将贩卖机委托给当前的状态对象,当调用某个方法时,再调用状态对象的方法。
封装的状态类图
我们已经知道每个状态下什么行为应该做什么,上面代码已经实现过了,现在只是把它们分散在各自类中。
定义状态接口
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
|
public interface State {
void chooseDrink();
void insertCoin();
void ejectCoin();
void returnDrink();
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 35 36 37 38 39 40 41 42 43 44 45
|
public class NoCoinState implements State {
private DrinkMachine drinkMachine;
public NoCoinState(DrinkMachine drinkMachine) { this.drinkMachine = drinkMachine; }
@Override public void chooseDrink() { drinkMachine.setCurrentState(drinkMachine.getChooseDrinkState()); System.out.println("饮料选择成功!请投币"); }
@Override public void insertCoin() { System.out.println("请先选择饮料!"); }
@Override public void ejectCoin() { System.out.println("请先投币!"); }
@Override public void returnDrink() { 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 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
|
@Data public class DrinkMachine {
private State noCoinState;
private State hasCoinState;
private State soldOutState;
private State soldState;
private State chooseDrinkState;
private int count = 0;
private State currentState;
public DrinkMachine(int count) { noCoinState = new NoCoinState(this); hasCoinState = new HasCoinState(this); soldOutState = new SoldOutState(this); soldState = new SoldState(this); chooseDrinkState = new ChooseDrinkState(this); if (count > 0) { currentState = noCoinState; } else { currentState = soldOutState; } }
public void chooseDrink() { currentState.chooseDrink(); }
public void insertCoin() { currentState.insertCoin(); }
public void ejectCoin() { currentState.ejectCoin(); }
public void returnDrink() { currentState.returnDrink(); }
public void dispense() { currentState.dispense(); }
}
|
上面只实现了一个状态的伪代码,其它状态类也差不多,也就是把一开始的代码“局部化”。上面版本中针对第一版本进行了以下优化:
定义
允许对象在内部状态改变时改变它的行为,对象看起来好像改变了它的类。
因为我们将状态封装成不同的类,并将动作委托到当前状态中,当状态改变时,行为也就变了
对于客户端而言,并不知道内部如何实现,看起来就好像一个新的实例。其实是通过引用不同状态来造成的假象
类图
状态模式的优缺点
优点
- 将不同行为“局部化”,符合开-闭原则
- 通过组合,委托的方式动态的改变行为
缺点
状态模式与策略模式
- 状态模式
- 我们将一群行为封装在状态对象中,Context可以随时委托到那些对象中的一个。随着时间流逝,当前状态在状态集合中游走,因此Context的行为也会跟着改变。但是对于Context的客户来说
这是浑然不觉的。
- 状态模式可以看成在Context中不用放很多判断条件,将不同的行为封装到不同的状态中,通过改变当前状态来改变行为
在固定的状态集合中游走,用户浑然不觉
- 策略模式
- 客户通常主动的指定要组合的策略对象是哪一个,摆脱继承的束缚,通过组合的方式动态的改变策略。
客户指定组合策略,比继承的更有弹性替代方案