状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
策略模式和状态模式时双胞胎,策略模式时围绕可以互换的算法来创建成功业务的。状态模式时通过改变对象内部的状态来帮助对象控制自己的行为。
下面我们看看状态模式的场景
场景1:有一个扭糖果机器,在投入1元硬币后,扭动曲柄,然后机器掉出一颗糖果。
先来看看场景,既然是状态模式,顾名思义,就是根据状态来划分,在这个过程中我们可以找出糖果机所有的状态:有钱,没钱,糖果售罄,出售糖果四种状态,而动作又:投入钱,退回钱,转到曲柄,发放糖果四种,现在我们根据这四种状态和四种动作来进行编码,如下:
//糖果机器类 public class GumballMachine { //定义状态的常量 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; //记录糖果机器中剩余糖果 int count = 0; public GumballMachine(int count){ this.count = count ; if(count > 0){ state = NO_QUARTER; } } //现在我们开始实现四个动作:投入钱,退回钱,转到曲柄,发放糖果 //投入钱 public void insertQuarter(){ switch(state){ //机器状态是有钱状态的时候,不能再投钱了 case HAS_QUARTER:System.out.println("你不能在投入第二个硬币了");break; //机器状态是没钱状态,投入后,机器状态变成有钱状态 case NO_QUARTER:state = HAS_QUARTER;System.out.println("你投入了1元钱");break; //机器状态是售罄 case SOLD_OUT:System.out.println("已经没有糖果了,不能投币了");break; //机器状态是出售糖果,为了让机器把状态给转换过来回复到NO_QUARTER case SOLD:System.out.println("请等待,正在准备糖果");break; } } //退回钱 public void ejectQuarter(){ switch(state){ //机器状态是有钱状态的时候,退钱 case HAS_QUARTER:System.out.println("退回钱");state = NO_QUARTER;break; //机器状态是没钱状态,就没的退 case NO_QUARTER:System.out.println("你并未投入钱");break; //机器状态是售罄 case SOLD_OUT:System.out.println("已经没有糖果了,不接受钱,更不可能退钱");break; //机器状态是出售糖果,就不能退了 case SOLD:System.out.println("正在出售糖果,无法退钱");break; } } //转动曲柄 public void turnCrank(){ switch(state){ //机器状态是有钱状态,扭动后改变状态为出售糖果,然后调用dispense方法来操作糖果的发放 case HAS_QUARTER:System.out.println("扭动成功");state = SOLD;dispense();break; //机器状态是没钱状态 case NO_QUARTER:System.out.println("你需要投入钱先");break; //机器状态是售罄 case SOLD_OUT:System.out.println("已经没有糖果了");break; //机器状态是出售糖果 case SOLD:System.out.println("正在出售糖果,此操作无效");break; } } //发放糖果 public void dispense(){ if(state == SOLD){ System.out.println("正在准备发放糖果"); count --; if(count == 0){ System.out.println("最后一颗糖果发放完了,糖果已售罄了"); state = SOLD_OUT; } else{ System.out.println("发放完了糖果"); state = NO_QUARTER; } } } }
让我们写个测试类来测试下
public class Test { public static void main(String[] args) { //准备5颗糖 GumballMachine gumballMachine = new GumballMachine(5); //投入钱 gumballMachine.insertQuarter(); //扭动曲柄 gumballMachine.turnCrank(); //在投入钱 gumballMachine.insertQuarter(); //退钱 gumballMachine.ejectQuarter(); //没钱的时候,扭动曲柄 gumballMachine.turnCrank(); //没钱的时候,退钱 gumballMachine.ejectQuarter(); //投入钱 gumballMachine.insertQuarter(); //再投入钱 gumballMachine.insertQuarter(); } }
运行结果如下
看起来状态模式就是这么简单了,但是如果扩展程序,加上其它状态那么我们就需要在四个动作中分别加入新的判断,这显得很麻烦,而且是不是也违背了我们的封装变化这原则呢,所以我们可以从新来设计下更合理的状态模式。
首先,我们定义一个State接口,在这个接口内,糖果机器每个动作都有一个对应的方法
//状态接口,每个状态都实现它里面的方法 public interface State { void insertQuarter(); void ejectQuarter(); void turnCrank(); void dispense(); }
然后为机器中的每个状态实现状态类,这些类负责在对应的状态下,机器会是怎么样的行为。
//四大状态之没有钱状态 public class NoQuarterState implements State{ GumballMachine gumballMachine; //通过构造器得到糖果机器的引用 public NoQuarterState(GumballMachine gumballMachine){ this.gumballMachine = gumballMachine; } @Override public void insertQuarter() { //因为确定是没有钱状态下投入钱,所以不需要理会其他状态 System.out.println("你投入了1元钱"); //将机器的状态变成有钱状态 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("你并未投入钱,所以没用在发放糖"); } } //四大状态之有钱状态 public class HasQuarterState implements State{ GumballMachine gumballMachine; public HasQuarterState(GumballMachine gumballMachine){ this.gumballMachine = gumballMachine; } @Override public void insertQuarter() { System.out.println("你不能在投入第二个硬币了"); } @Override public void ejectQuarter() { System.out.println("退回钱了"); gumballMachine.setState(gumballMachine.getNoQuarterState()); } @Override public void turnCrank() { System.out.println("扭动成功"); gumballMachine.setState(gumballMachine.getSoldState()); } @Override public void dispense() { System.out.println("操作错误,机器还没到出售糖果的阶段"); } } //四大状态之出售状态 public class SoldState implements State{ 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){ System.out.println("发放糖果完成"); 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; } @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("已经没有糖果了,此操作状态无效"); } }
最后我们将之前旧代码的条件取而代之为动作委托,委托到相应的状态类。
//糖果机器类 public class GumballMachine { //将所有状态都定义都机器中 State soldState; State soldOutState; State noQuarterState; State hasQuarterState; State state = soldOutState; int count = 0; public GumballMachine(int count){ soldOutState = new SoldOutState(this); soldState = new SoldState(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 releaseBall(){ System.out.println("正在发放糖果中..."); if(count !=0){ count -- ; } } //get/set方法 }
测试代码不变,我们可以看到结果如下:
好了对比之前的,我们做了以下几点:
1.将每个状态的行为局部化到它自己的类中
2.将容易产生问题的判断条件删除,以方便日后的维护
3.让每个状态对修改关闭,对扩展开发,即使加入新的状态也没关系
最后,让我们再来区分区分状态、策略、模板方法模式。
状态:封装基于状态的行为,并将行为委托到当前状态。
策略:将可以互换的行为封装起来,然后使用委托的方法决定使用哪一个行为。
模板方法:由子类决定如何实现算法中的某些步骤。
下一节:代理模式