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

评论

您确定要删除吗?删除之后不可恢复