状态模式 State Pattern

2020-03-21

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

《Head First 设计模式》举的例子是糖果机:糖果机定义了投币、退币、转动曲柄、发放糖果等行为,也有未投币、已投币、售出、已售罄等状态,可以使用 int 常量或枚举来标识这些状态。在客户端对糖果机发出请求时,糖果机内部每种行为都要使用大量的 if 语句进行状态判断和切换,不仅混乱,而且不利于扩展,例如新增一个状态就要大量修改原有代码。

状态模式将“状态”抽象出来,将每种状态下的行为下放到具体状态类,这对客户端是透明的。网上有些文章把状态的构建和切换放到了客户端,这就违背了初衷。

实例

参与者

  • Context 环境,定义客户端所需的接口,维护一个 State 状态对象

  • State 抽象状态,定义与 Context 的一个特定状态相关行为的接口,任何状态都实现这个接口,状态之间可以互相替换

  • ConcreteState subclasses 具体状态子类,处理来自 Context 的请求,每个类都提供了自己对请求的实现,所以当 Context 状态改变时行为也跟着改变

源码

public class Context {
    private State mState;
    private State mStateA;
    private State mStateB;

    void setState(State state) {
        mState = state;
    }

    State getStateA() {
        return mStateA;
    }

    State getStateB() {
        return mStateB;
    }

    public Context() {
        mStateA = new ConcreteStateA(this);
        mStateB = new ConcreteStateB(this);
        mState = mStateA;
    }

    public void request() {
        mState.handle();
    }
}
public interface State {
    void handle();
}
public class ConcreteStateA implements State {
    private Context mContext;

    public ConcreteStateA(Context context) {
        mContext = context;
    }

    @Override
    public void handle() {
        new Throwable().printStackTrace();
        mContext.setState(mContext.getStateB());
    }
}
public class ConcreteStateB implements State {
    private Context mContext;

    public ConcreteStateB(Context context) {
        mContext = context;
    }

    @Override
    public void handle() {
        new Throwable().printStackTrace();
        mContext.setState(mContext.getStateA());
    }
}

状态模式可以有不同的写法,这里是在 Context 中为每种状态维护了一个实例,也可以把具体状态写成单例模式。

测试

Context context = new Context();
context.request();
context.request();
java.lang.Throwable
	at io.binac.designpattern.state.ConcreteStateA.handle(ConcreteStateA.java:12)
	at io.binac.designpattern.state.Context.request(Context.java:27)
...
java.lang.Throwable
	at io.binac.designpattern.state.ConcreteStateB.handle(ConcreteStateB.java:12)
	at io.binac.designpattern.state.Context.request(Context.java:27)
...

与策略模式比较

两者都可以在运行时改变行为,但策略模式中客户端通常会主动指定所需的策略对象,而状态模式中状态对象对客户端是透明的,客户不需要了解状态。

源码实现

https://github.com/qianbinbin/DesignPattern/tree/master/src/main/java/io/binac/designpattern/state

参考资料

  1. 《Head First 设计模式》
  2. State pattern - Wikipedia
Java设计模式

本作品根据 署名-非商业性使用-相同方式共享 4.0 国际许可 进行授权。

缩减 IMG 镜像文件

中介者模式 Mediator Pattern