一、了解状态模式
1.1 什么是状态模式
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它自己的类。
状态模式将状态封装为独立的类,并将动作委托到代表当前状态的对象。
1.2 状态模式组成结构
- 上下文 (Context):用户对象,拥有(聚合)一个 State 类型的成员,以标识对象的当前状态。
- 抽象状态 (State):接口或基类,封装与 Context 的特定状态相关的行为。
- 具体状态 (ConcreteState):接口实现类或子类,实现了一个与 Context 某个状态相关的行为。
1.3 状态模式 UML 图解
1.4 状态模式与策略模式
如果对策略模式也了解的话,你会发现状态模式的类图与策略模式的类图几乎是一样的。它们之间有什么关系吗?
PS:策略模式具体博文http://blog.youkuaiyun.com/codejas/article/details/79077186
虽然状态模式与策略模式的类图是一样的,但是它们两个的“意图”是明显不一样的。
对于状态模式而言,我们将一群行为封装在状态对象中,context 的行为随时可委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出 context 内部的状态,因此,context 的行为也会随着改变。但是 context 的客户对于状态对象的了解程度不多,甚至根本是浑然不知。
而对于策略模式而言,客户通常主动指定 context 所需要的策略对象是哪一个。策略模式虽然可以让我们的代码更有弹性,但是对于某个 context 对象来说,通常都只有一个最适当的策略对象。
1.5 状态模式应用场景
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
- 一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态。
二、状态模式具体应用
2.1 问题描述
糖果机:一种新型糖果机的实现控制流程如图所示,现在需要我们用 Java 语言来实现它。
2.2 问题分析
从上面这个图中我们可以找出所有的状态:没有一块钱、有一块钱、售出糖果盒糖果告罄。也就是上图中的圆圈部分。为了提高程序的扩展性和维护性,我们采用状态模式来设计代码。将四个状态映射为对应的四个类。
我们需要做的事情如下:
- 首先定义一个 State 接口。在这个接口内,糖果机的每个动作都有一个对应的方法。
- 然后为机器中的每个状态实现状态类。这些类将负责在对应的状态下为糖果机工作。
- 将动作委托到状态类。
2.3 状态模式设计类图
2.4 代码实现
上下文糖果机 CandyMachine 类
package com.jas.state;
/**
* 糖果机类
* @author Jas
* @create 2018-01-31 10:35
**/
public class CandyMachine {
State noCoinState;
State hasCoinState;
State soldState;
State soldOutState;
State state = soldOutState;
/** 刚开始糖果机中是没有糖果的,所以 count 为 0 */
int count = 0;
/**
* 通过构造函数实例化所有状态对象
* @param numberCandys 初始化糖果机中的糖果数量
*/
public CandyMachine(int numberCandys){
noCoinState = new NoCoinState(this);
hasCoinState = new HasCoinState(this);
soldState = new SoldState(this);
soldOutState = new SoldOutState(this);
this.count = numberCandys;
//如果超过 0 颗糖果,就把状态设为 noCoinState
if(numberCandys > 0){
state = noCoinState;
}
}
public void insertCoin(){
state.insertCoin();
}
public void ejectCoin(){
state.ejectCoin();
}
public void turnCrank(){
state.turnCrank();
state.dispense();
}
/**
* 该方法用于释放糖果,并将 count 的数量减一
*/
void releaseBall(){
System.out.println("糖果机释放一个糖果 ...");
if (count != 0){
count --;
}
}
public State getNoCoinState() {
return noCoinState;
}
public State getHasCoinState() {
return hasCoinState;
}
public State getSoldState() {
return soldState;
}
public State getSoldOutState() {
return soldOutState;
}
public int getCount() {
return count;
}
public void setState(State state){
this.state = state;
}
}
抽象状态 State 接口
package com.jas.state;
/**
* 所有的状态接口
* @author Jas
* @create 2018-01-31 10:28
**/
public interface State {
/** 投入一个硬币 */
void insertCoin();
/** 弹出硬币 */
void ejectCoin();
/** 转动曲柄 */
void turnCrank();
/** 糖果分发或告罄 */
void dispense();
}
具体状态 NoCoinState 类
package com.jas.state;
/**
* @author Jas
* @create 2018-01-31 10:30
**/
public class NoCoinState implements State {
CandyMachine candyMachine;
public NoCoinState(CandyMachine candyMachine){
this.candyMachine = candyMachine;
}
/**
* 投入硬币后,将糖果机置为有一个硬币的状态
*/
@Override
public void insertCoin() {
System.out.println("你投入了一个硬币!");
candyMachine.setState(candyMachine.getHasCoinState());
}
@Override
public void ejectCoin() {
System.out.println("你还没有投入一个硬币!");
}
@Override
public void turnCrank() {
System.out.println("即使你转动了曲柄,但是不会出糖果,因为你没有投入硬币!");
}
@Override
public void dispense() {
System.out.println("你需要先投入一个硬币!");
}
}
具体状态 HasCoinState 类
package com.jas.state;
/**
* @author Jas
* @create 2018-01-31 10:39
**/
public class HasCoinState implements State {
CandyMachine candyMachine;
public HasCoinState(CandyMachine candyMachine){
this.candyMachine = candyMachine;
}
@Override
public void insertCoin() {
System.out.println("你已经投入了一个硬币,不能再投入硬币了!");
}
/**
* 在糖果机有一个硬币的时候,你可以申请退回你的硬币,并改变糖果机的状态
*/
@Override
public void ejectCoin() {
System.out.println("您的硬币已退回!");
candyMachine.setState(candyMachine.getNoCoinState());
}
/**
* 糖果机有硬币的时候,你可以转动曲柄,从而获得糖果,并改变糖果机的状态
*/
@Override
public void turnCrank() {
System.out.println("你转动了曲柄,请稍等 ...");
candyMachine.setState(candyMachine.getSoldState());
}
@Override
public void dispense() {
System.out.println("现在还没有糖果发放!");
}
}
具体状态 SoldState 类
package com.jas.state;
/**
* @author Jas
* @create 2018-01-31 10:40
**/
public class SoldState implements State {
CandyMachine candyMachine;
public SoldState(CandyMachine candyMachine){
this.candyMachine = candyMachine;
}
@Override
public void insertCoin() {
System.out.println("现在不能投入硬币了,请稍等,马上为您发放一个糖果!");
}
@Override
public void ejectCoin() {
System.out.println("您已经转动了曲柄,现在不能再退回硬币了!");
}
@Override
public void turnCrank() {
System.out.println("您已经转动了曲柄一次,多余的转动是无效的!");
}
/**
* 分发糖果,并根据糖果机中的糖果数量,改变糖果机的状态
*/
@Override
public void dispense() {
candyMachine.releaseBall();
//如果糖果机中有糖果,将糖果机置为初始没有硬币的状态
if(candyMachine.getCount() > 0){
candyMachine.setState(candyMachine.getNoCoinState());
}else {
System.out.println("糖果已经售卖完了,请改天再来!");
candyMachine.setState(candyMachine.getSoldOutState());
}
}
}
具体状态 SoldOutState 类
package com.jas.state;
/**
* @author Jas
* @create 2018-01-31 10:41
**/
public class SoldOutState implements State {
CandyMachine candyMachine;
public SoldOutState(CandyMachine candyMachine){
this.candyMachine = candyMachine;
}
@Override
public void insertCoin() {
System.out.println("请不要再投入硬币了,糖果已经卖完了!");
}
@Override
public void ejectCoin() {
System.out.println("您没有投入硬币,所以现在不能弹出硬币!");
}
@Override
public void turnCrank() {
System.out.println("即使你转动了曲柄,但还是不会出糖果,因为已经没有糖果了!");
}
@Override
public void dispense() {
System.out.println("糖果已经卖完了,没有糖果用于发放了!");
}
}
测试类
package com.jas.state;
/**
* @author Jas
* @create 2018-01-31 11:04
**/
public class CansyMachineTestDrive {
public static void main(String[] args) {
//初始化糖果机中的糖果数量为 1 个
CandyMachine candyMachine = new CandyMachine(1);
candyMachine.insertCoin();
candyMachine.ejectCoin();
System.out.println("======================");
candyMachine.insertCoin();
candyMachine.turnCrank();
System.out.println("======================");
candyMachine.insertCoin();
candyMachine.turnCrank();
}
}
/**
* 输出
* 你投入了一个硬币!
* 您的硬币已退回!
* ======================
* 你投入了一个硬币!
* 你转动了曲柄,请稍等 ...
* 糖果机释放一个糖果 ...
* 糖果已经售卖完了,请改天再来!
* ======================
* 请不要再投入硬币了,糖果已经卖完了!
* 即使你转动了曲柄,但还是不会出糖果,因为已经没有糖果了!
* 糖果已经卖完了,没有糖果用于发放了!
*/
2.5 问题总结
在上面这个例子中,客户并不直接与具体状态类直接交互,而是通过糖果机类向状态类发出请求,还有要注意的是,客户并不会直接改变糖果机的状态。了解状态的是糖果机类,客户并不了解,所以客户不会直接与状态类交互。在糖果机类中有一个状态实例,用这个实例来完成状态之间不同的切换。
三、 状态模式总结
3.1 状态模式的优缺点
优点
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前需要确定状态种类。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
3.2 状态模式知识总结
- 状态模式允许一个对象基于内部的状态而拥有不同的行为。
- 状态模式用类来表示状态。
- 上下文 (Context) 会将行为委托给当前的状态对象。
- 状态模式和策略模式虽然有相同的类图,但是它们的意图是不一样的。
- 状态模式允许上下文随着状态的改变而改变行为。
- 使用状态模式通常会导致类的数量大量增加。
- 状态类可以被多个上下文 (Context) 实例共享。
PS:点击了解更多设计模式 http://blog.youkuaiyun.com/codejas/article/details/79236013
参考文献
《Head First 设计模式》
http://www.runoob.com/design-pattern/state-pattern.html