设计模式之状态模式
策略模式是围绕可以互换的算法来创建成功业务的,然而,状态走的是更崇高的路,它通过改变对象内部的状态来帮助对象控制自己的行为。
定义状态模式
先看看定义:状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类
问题引入
自动糖果售卖机,糖果机的控制器需要的工作流程如下图

状态机101
我们如何从状态图得到真正的代码呢?下面是一个实现状态机(state machine)的简单介绍。
1.首先,找到所有的状态:一共四个状态

2.接下来,创建一个实例变量来持有目前的状态,然后定义每个状态的值:
//每个状态用不同的值表示
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;
3.现在,我们将所有系统中可以发生的动作整合起来:

4.现在我们创建一个类,它的作用就像是一个状态机,每一个动作,我们都创建了一个对应的方法,这些方法利用条件语句来决定在每个状态内什么行为是恰当的。比如对“投入25分钱”这个动作来说,我们可以把对应方法写成下面的样子:
public void insertQuarter() {
// 每一个可能的状态都需要用条件语句检查......
if (state == HAS_QUARTER) {
// 然后对每一个可能的状态展现适当的行为......
System.out.println("You can't insert another quarter");
} else if (state == SOLD_OUT) {
System.out.println("You can'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");
} else if (state == NO_QUARTER) {
// // 但是也可队转换到另一个状态,像状态图中所描绘的那样。
state = HAS_QUARTER;
System.out.println("You inserted a quarter");
}
}
技巧:如何对对象内的状态建模——通过创建一个实例变量来持有状态值,并在方法内书写条件代码来处理不同状态。
初步代码
class GumballMachine{
final static int SOLD_OUT=0; // 糖果售罄
final static int NO_QUARTER=1; // 没有投入25分钱
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;
}
}
//当有25分钱投入,就会执行这个方法
public void insertQuarter(){
if(state==HAS_QUARTER){
System.out.println("如果已投入过25分钱,我们就告诉顾客");
}else if(state==NO_QUARTER){
state=HAS_QUARTER;
System.out.println("如果是在“没有25分钱”的状态下,我们就接收25分钱," +"并将状态转换到“有25分钱”的状态");
}else if(state ==SOLD_OUT){
System.out.println("如果糖果已经售罄,我们就拒绝收钱");
}else if(state==SOLD){
System.out.println("如果顾客刚才买了糖果,就需要稍等一下,好让状态转换完毕。" +"恢复到“没有25分钱”的状态");
state=NO_QUARTER;
}
}
//如果顾客试着退回25分钱就执行这个方法
public void ejectQuarter(){
if(state==HAS_QUARTER){
System.out.println("如果有25分钱,我们就把钱退出来,回到“没有25分钱”的状态");
state=NO_QUARTER;
}else if(state==NO_QUARTER){
System.out.println("如果没有25分钱的话,当然不能退出25分钱");
}else if(state ==SOLD){
System.out.println("顾客已经转动曲柄就不能再退钱了,他已经拿到糖果了");
}else if(state==SOLD_OUT){
System.out.println("如果糖果售罄,就不能接受25分钱,当然也不可能退钱");
}
}
//顾客试着转动曲柄
public void turnCrank(){
if(state==SOLD){
System.out.println("别想骗过机器拿两次糖果");
}else if(state==NO_QUARTER){
System.out.println("我们需要先投入25分钱");
}else if(state ==SOLD_OUT){
System.out.println("我们不能给糖果,已经没有任何糖果了");
}else if(state==HAS_QUARTER){
System.out.println("成功,他们拿到糖果了," +"改变状态到“售出糖果”然后调用机器的disoense()方法");
state=SOLD;
dispense();
}
}
//调用此方法,发放糖果
public void dispense(){
if(state==SOLD){
System.out.println("我们正在“出售糖果”状态,给他们糖果");
count=count-1;
// 在这里处理“糖果售罄”的情况,
// 如果这是最后一个糖果,将机器的状态设置到“糖果售罄”否则就回到“没有25分钱”的状态
if(count==0){
System.out.println();
state=SOLD_OUT;
}else{
state=NO_QUARTER;
}
}else if(state==SOLD_OUT){
System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
}else if(state ==HAS_QUARTER){
System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
}else if(state==NO_QUARTER){
System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
}
}
}
新的设计
尽管程序完美运行,但还是躲不掉需求变更的命运
现在糖果公司要求:当曲柄被转动时,有10%的几率掉下来的是两个糖果。(氪金扭蛋)
再回看一下我们的初步代码,想要实现新的需求将会变得非常麻烦:
- 必须新增一个中奖的“赢家”状态。
- 必须在每一个方法添加新的判断条件来处理“赢家”状态。
- 转动把手的方法中还需要检查目前状态是否是“赢家”再决定切换到“赢家”状态行为还是正常出售行为。
在现有代码基础上做增加将会很麻烦,也不利与以后的维护,扩展性差。
回顾一下第一章的策略模式中的设计原则:
找出应用中可能需要变化之处,把他们独立出来
将状态独立出来,封装成一个类,都实现State接口。
类图

新的设计想法如下:
- 首先,我们定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法
- 然后为机器的每个状态实现状态类,这些类将负责在对应的状态下进行机器的行为
- 最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类

代码
定义一个State接口
public interface State { // 没有共同的功能可以放进抽象类中,就会使用接口。
public void insertQuarter();//投币
public void ejectQuarter();//退币
public void turnCrank();//转动出货把手
public void dispense();//出售
}
为机器的每个状态实现状态类:
//未投币状态
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
public void insertQuarter() {
System.out.println("你投入一枚硬币");
gumballMachine.setState(gumballMachine.getHasQuarterState());//状态转换为已投币状态
}
public void ejectQuarter() {
System.out.println("你未投币,无法退钱");
}
public void turnCrank() {
System.out.println("未投币,请先投币");
}
public void dispense() {
System.out.println("请先投币");
}
}
//已投币状态
public class HasQuarterState implements State {
// 首先我们增加一个随机数产生器,产生10%赢的机会
Random randomWinner=new Random(System.currentTimeMillis());
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
public void insertQuarter() {
System.out.println("已投币,无法再接收投币");
}
public void ejectQuarter() {
System.out.println("已退币");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public void turnCrank() {
System.out.println("已转动把手,糖果出售中。。。。");
int winner=randomWinner.nextInt(10);//随机数生成,用以标记“赢家-10%”状态
if((winner==0)&&(gumballMachine.getCount()>1))
gumballMachine.setState(gumballMachine.getWinnerState());
else
gumballMachine.setState(gumballMachine.getSoldState());
}
public void dispense() {
System.out.println("机器中已经没有糖果可以出售了!");
}
}
//出售状态
public class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
public void insertQuarter() {
System.out.println("请等候,正在初始化机器中");
}
public void ejectQuarter() {
System.out.println("抱歉,您已转动把手获得了糖果,无法退币");
}
public void turnCrank() {
System.out.println("您重复转动把手,无法再获取更多糖果");
}
public void dispense() {
gumballMachine.releaseBall();//出货,糖果-1
if(gumballMachine.getCount()>0)
gumballMachine.setState(gumballMachine.getNoQuarterState());
else {
System.out.println("糖果已售完");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
//售罄状态
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
public void insertQuarter() {
System.out.println("此机器的糖果已售完,不接收投币");
}
public void ejectQuarter() {
System.out.println("未投币,退币失败");
}
public void turnCrank() {
System.out.println("糖果已售完,转动把手也不会有糖果出来的");
}
public void dispense() {
System.out.println("机器中已无糖果");
}
}
//赢家状态
public class WinnerState implements State {
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine) {
this.gumballMachine=gumballMachine;
}
public void insertQuarter() {
System.out.println("请等候,正在初始化机器中");
}
public void ejectQuarter() {
System.out.println("抱歉,您已转动把手获得了糖果");
}
public void turnCrank() {
System.out.println("您重复转动把手,无法再获取更多糖果");
}
public void dispense() {
System.out.println("恭喜你成为幸运儿,你将额外获得一个免费糖果");
gumballMachine.releaseBall();//出货,糖果-1
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());
}
}
}
}
糖果机类:
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;
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);
winnerState=new WinnerState(this);
this.count=numberGumballs;
if(numberGumballs>0)
state=noQuarterState;//先判断条件再改变状态
}
//将动作委托到状态类
public void insterQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
//请注意,我们不需要在GumballMachine中准备一个dispense()的动作方法,
//因为这只是一个内部的动作;用户不可以直接要求机器发放糖果。但我们是在状态对象的
//tuznCtank()方法中调用dispense()方法的。
}
//获取当前状态
public State getHasQuarterState() {
return hasQuarterState;
}
//改变状态
public void setState(State state) {
this.state=state;
}
public void releaseBall() {
System.out.println("糖果从出口售出");
if(count!=0)
count-=1;
}
public State getSoldOutState() {
return soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getSoldState() {
return soldState;
}
//获取糖果机中糖果数量
public int getCount() {
return count;
}
public State getWinnerState() {
return winnerState;
}
public String toString() {
// TODO 自动生成的方法存根
String s="剩余糖果:"+count;
return s;
}
}
该进之处
- 将每个状态的行为局部化到它自己的类中。将容易产生问题的if语句删除,以方便日后的维护。
- 让每一个状态“对修改关闭”,让糖果机“对扩展开放”,因为可以加入新的状态类(我们马上就这么做)。
- 创建一个新的代码基和类结构,这更能映射万能糖果公司的图,而且更容易阅读和理解。
状态模式的类图

状态模式的类图其实和策略模式完全一样!
状态模式与策略模式
这两个模式的差别在于它们的“意图”
- 以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个,随着时间而流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,context的行为也会跟着改变,但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。
- 以策略模式而言,客户通常主动指定Context所要组合的策略对象时哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最适当的策略对象。
- 一般的,我们把策略模式想成是除了继承之外的一种弹性替代方案,如果你使用继承定义了一个类的行为,你将被这个行为困住,是指要修改它都很难,有了策略模式,你可以通过组合不同的对象来改变行为。
- 我们把状态模式想成是不用在context中放置许多条件判断的替代方案,通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为。
模式区分
- 状态模式:封装基于状态的行为,并将行为委托到当前状态
- 策略模式:将可以互换的行为封装起来。然后使用委托的方法,觉得使用哪一个行为
- 模板方法模式:由子类决定如何实现算法中的某些步骤
要点
(1)状态模式允许一个对象基于内部状态而拥有不同的行为。
(2)和程序状态机(PSM)不同,状态模式用类来表示状态。
(3)Context会将行为委托给当前状态对象。
(4)通过将每一个状态封装进一个类,我们把以后需要做的任何改变局部化了。
(5)状态模式和策略模式有相同的类图,但是他们的意图不同。
(6)策略模式通常会用行为或算法配置Context类。
(7)状态模式允许Context随着状态的改变而改变行为。
(8)状态转换可以有State类或Context类控制。
(9)使用状态模式通常会导致设计中类的数目大量增加。
(10)状态栏可以被多个Context实例共享。

浙公网安备 33010602011771号