Java设计模式-状态模式
状态(State)模式有时也称为状态对象(Objects for States)模式,在23种设计模式中属于对象行为型模式。
《GOF设计模式》一书中是这样定义状态模式的,状态模式允许一个对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
适用性
对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常有多个操作包含这一相同的条件结构。状态模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
先看第一条,对象的行为依赖它的状态,文字描述也很抽象,但是如果仔细斟酌一下,会发现平常开发中遇到场景也不少,比如网络状态、播放器状态以及登录注册等。播放器状态比较容易理解,有开始播放、播放中、暂停、停止等状态,这里就以Android中VideoView来说,如果查看源码可以发现,在VideoView中并没有使用状态模式,Android系统库的开发人员不可能不了解设计模式,这也是学习设计模式需要Get到的地方,设计模式虽好,可是不能一味的依赖设计模式,若使用不当,只会无端增加系统类和对象的个数,导致程序结构和代码混乱。
第二条可能比较容易理解,一个中大型项目中遇到几处庞大的分支条件语句可以说是司空见惯。如果在良好架构的基础上,再使用上状态模式,那么项目的结构和代码会整洁许多,特别是在有多人协作的团队,一旦有新增的行为,只需要新增一个自己的状态类即可,可能根本不需要去修改核心控制类代码,这就是设计模式的好处。
示例介绍
想要理解一个设计模式,使用示例的方式可能再好不过。
本文以一个糖果机为示例,涉及的状态如下图:
public class CandyMachine { final static int NO_CANDY = 1000; final static int NO_COIN = 1001; final static int HAS_COIN = 1002; final static int DISPENSE = 1003; int count; int state = NO_CANDY; public CandyMachine(int count) { this.count = count; if (this.count > 0) { state = NO_COIN; } } // 投币 public void insertCoin() { if (state == HAS_COIN) { System.out.println("Coin already inserted"); } else if (state == NO_COIN) { state = HAS_COIN; } else if (state == NO_CANDY) { System.out.println("No candies available"); } else if (state == DISPENSE) { System.out.println("Error. System is currently dispensing"); } } // 按钮 public void pressButton() { if (state == HAS_COIN) { state = DISPENSE; } else if (state == NO_COIN) { System.out.println("No coin inserted"); } else if (state == NO_CANDY) { System.out.println("No candies available"); } else if (state == DISPENSE) { System.out.println("No coin inserted"); } } // 售出糖果 public void dispense() { if (state == HAS_COIN) { System.out.println("No candies rolled out"); } else if (state == NO_COIN) { System.out.println("No coin inserted"); } else if (state == NO_CANDY) { System.out.println("No candies available"); } else if (state == DISPENSE) { count = count - 1; if (count == 0) { state = NO_CANDY; } else { state = NO_COIN; } } } }
糖果机程序设计好了,接下来就是维护与扩展的事情了,假设有一个厂商需要加入一些激励措施,比如有十分之一的概率,在售出糖果时,糖果机可以一次出两颗糖,那么,现在需要在CandyMachine中新加入一个Winner状态。在本示例中,好像还不是太复杂,只需要在每一个方法中多加入一个条件语句即可。
但是,问题来了,现在是加入一种状态,如果接下来又有新的状态需要加入了,现在示例CandyMachine中方法数还不多,将来继续扩展,当有一天方法数也很可观,在这个基础上增加新的状态,每次加入新的状态都要更改CandyMachine类中的每一个方法,严重违反了软件设计的开闭原则。
接下来,将示例按照状态模式设计,看一下后续的扩展性如何。
首先,将所有的公有的方法抽取到一个State接口中,然后所有的状态类都必须实现该接口。
public interface State { void insertCoin(); void pressButton(); void dispense(); } public class NoCoinState implements State { private CandyMachine machine; public NoCoinState(CandyMachine machine){ this.machine=machine; } @Override public void insertCoin() { machine.setState(machine.getContainsCoinState()); } @Override public void pressButton() { System.out.println("No coin inserted"); } @Override public void dispense() { System.out.println("No coin inserted"); } @Override public String toString(){ return "NoCoinState"; } } ... public class DispensedState implements State { private CandyMachine machine; public DispensedState(CandyMachine machine){ this.machine=machine; } @Override public void insertCoin() { System.out.println("Error. System is currently dispensing"); } @Override public void pressButton() { System.out.println("Error. System is currently dispensing"); } @Override public void dispense() { if(machine.getCount()>0) { System.out.println("Oops, out of a candy!"); machine.setState(machine.getNoCoinState()); machine.setCount(machine.getCount()-1); } else{ System.out.println("No candies available"); machine.setState(machine.getNoCandyState()); } } @Override public String toString(){ return "DispensedState"; } } public class CandyMachine { private State noCoinState; private State noCandyState; private State dispensedState; private State hasCoinState; private State state; private int count; public CandyMachine(int count){ noCoinState=new NoCoinState(this); noCandyState=new NoCandyState(this); dispensedState=new DispensedState(this); hasCoinState=new HasCoinState(this); state = noCoinState; this.count=count; } public void refillCandy(int count){ this.count+=count; this.state=noCoinState; } public void ejectCandy(){ if(count!=0){ count--; } } public void insertCoin(){ System.out.println("You inserted a coin."); state.insertCoin(); } public void pressButton(){ System.out.println("You have pressed the button."); state.pressButton(); state.dispense(); } ... @Override public String toString(){ String machineDef="Current state of machine "+state +". Candies available "+count; return machineDef; } }
现在扩展一种Winner状态,部分代码如下:
public class WinnerState implements State { private CandyMachine machine; ... @Override public void dispense() { if (machine.getCount() > 1) { System.out.println("Oops, out of two candies!"); machine.setState(machine.getNoCoinState()); machine.setCount(machine.getCount() - 2); } else { System.out.println("No candies available"); machine.setState(machine.getNoCandyState()); } } ... } public static void main(String[] args) { CandyMachine machine=new CandyMachine(10); System.out.println(machine); machine.insertCoin(); machine.pressButton(); System.out.println("--------------------------"); machine.insertCoin(); machine.pressButton(); System.out.println("--------------------------"); machine.insertCoin(); machine.pressButton(); System.out.println("--------------------------"); System.out.println(machine); } Current state of machine NoCoinState. Candies available 10 You inserted a coin. You have pressed the button. Oops, out of a candy! -------------------------- You inserted a coin. You have pressed the button. Oops, out of a candy! -------------------------- You inserted a coin. You have pressed the button. Oops, out of a candy! -------------------------- Current state of machine NoCoinState. Candies available 7
模式分析
状态模式类图如下:
- Context:上下文,有时又称环境类,它可以拥有一些自己的状态,在上述示例中表示CandyMachine类。在Context类中,一般对外提供的并不是setState()这种设置状态的方法,状态变换是在Context类中相互转换的,外部一般调用的是类图中的行为方法,如类图中request()方法。
- State接口:定义了所有具体状态的公有接口,任何状态都要实现这个接口,这样各个状态之间可以相互转换。
- ConcreteState:是一个用于处理Context请求的具体状态,每一个ConcreteState都提供了它自己对于请求的实现。所以当Context改变状态时行为也跟着改变。
在上述示例中,可以看出总是ConcreteState决定了接下来的状态是什么,但是在实际开发中,并不总是有ConcreteState决定接下里状态流向,也有可能是Context决定状态流向。
外部使用Context时并不会直接与状态直接交互,一定要注意这里,外部场景调用的始终是Context的行为方法,而不是setState()设置状态类型的方法。状态是在Context中来表示它的内部行为的,所以只有Context自己才会对状态有所更改,调用者是不会直接改变Context状态的。
如果在一个系统中,Context实例不止一个,这些实例的状态也是可以共享的,需要注意的是,在Context中的各种状态要设置为静态的,否则是不能共享的。
状态模式优缺点
状态模式优点
枚举了所有可能的状态,有利于规划转换整体状态。
将所有与某个状态相关的行为放到了一个类中,并且可以方便新增状态,只需要改变对象的状态就可以改变行为。
将状态的转换逻辑与状态对象合为一体,而不是一个巨大的条件语句。
状态模式缺点
状态模式会增加系统中类和对象个数。
状态模式的结构和实现比较复杂,如果使用不当将导致程序结构和代码混乱。
状态模式对“开闭原则”支持不是很好,对于可以切换的状态模式,一旦增加新的状态,需要更改那些负责状态转换的源代码,否则无法切换的新增的状态行为。
与其它模式对比
策略模式相当于对某一种行为使用不同算法的设计,算法之间是是平等的,可以相互替换。而状态模式是对某个对象所有行为之间的设计,不同行为有不同的状态,将行为委托到状态。前者是局部行为,而后者是为全局行为设计。
如果一个对象只有一个行为,这时候是可以直接使用策略模式代替状态模式的,因为它本身也就一个状态。
小结
状态模式在使用中确实会带来不小的便利,但是缺点也很明显,导致系统设计比较臃肿,增加了系统中类和对象的个数。一句话,设计模式虽好,可不要贪杯哦。
所有的设计都应该在有帮助时遵守,比如抽象与速度之间的取舍,空间和时间上的平衡等。
示例源代码下载,提取码:xjp6