简述

先看维基百科上的一段定义:

The state pattern, which closely resembles Strategy Pattern, is a behavioral software design pattern, also known as the objects for states pattern. This pattern is used in computer programming to encapsulate varying behavior for the same object based on its internal state. This can be a cleaner way for an object to change its behavior at runtime without resorting to large monolithic conditional statements and thus improve maintainability.

状态模式酷似策略模式,是一种行为软件设计模式。这种模式用于封装对象基于其内部状态的不同行为。这可能是一种更为简洁的行为对于对象来改变其运行时的行为,而不是诉诸大量的条件语句因此提高了其可维护性。

在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理。最直接的解决方案是将这些所有可能发生的情况全都考虑到。然后使用if… ellse语句来做状态判断来进行不同情况的处理。但是对复杂状态的判断就显得“力不从心了”。随着增加新的状态或者修改一个状体(if else(或switch case)语句的增多或者修改)可能会引起很大的修改,而程序的可读性,扩展性也会变得很弱。维护也会很麻烦。那么我就考虑只修改自身状态的模式。

例子1:按钮来控制一个电梯的状态,一个电梯开们,关门,停,运行。每一种状态改变,都有可能要根据其他状态来更新处理。例如,开门状体,你不能在运行的时候开门,而是在电梯定下后才能开门。

例子2:我们给一部手机打电话,就可能出现这几种情况:用户开机,用户关机,用户欠费停机,用户消户等。 所以当我们拨打这个号码的时候:系统就要判断,该用户是否在开机且不忙状态,又或者是关机,欠费等状态。但不管是那种状态我们都应给出对应的处理操作。

在下面的两种情况下均可使用State模式:

1)一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。

2)代码中包含大量与对象状态有关的条件语句:一个操作中含有庞大的多分支的条件(if else(或switch case)语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。 State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

UML图

可以看到组成元素:

环境类(Context): 定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态。

抽象状态类(State): 定义一个接口以封装与Context的一个特定状态相关的行为。

具体状态类(ConcreteState): 每一子类实现一个与Context的一个状态相关的行为。

代码实例

下面一个例子模拟了实际的交通灯的情景:

package com.ans;

interface State {
    public void change(Light light);
}

class Light {
    private State state;

    private void change() {
        state.change(this);
    }

    public void work() {
        while (true) {
            change();
        }
    }

    public void setState(State state) {
        this.state = state;
    }
}

class GreenState implements State {
    public void change(Light light) {
        try {
            System.out.println("现在是绿灯,放行");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        light.setState(new YellowState());
    }
}

class RedState implements State {
    public void change(Light light) {
        try {
            System.out.println("现在是红灯,禁行");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        light.setState(new GreenState());
    }

}

class YellowState implements State {
    public void change(Light light) {
        try {
            System.out.println("现在是黄灯,等待");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        light.setState(new RedState());
    }
}

public class StatePattern {

    public static void main(String[] args) {
        Light light = new Light();
        light.setState(new GreenState());// 初始化为绿灯状态
        light.work();
    }

}

运行结果:

现在是绿灯,放行
现在是黄灯,等待
现在是红灯,禁行
现在是绿灯,放行
现在是黄灯,等待
现在是红灯,禁行

下面是一个电梯的例子:

package com.ans;

abstract class LiftState {
    public abstract void open();

    public abstract void close();

    public abstract void stop();

    public abstract void run();
}

class OpenState extends LiftState {
    public void close() {
        System.out.println("Lift is ready to close");
    }

    @Override
    public void open() {
        System.out.println("Lift is opening..");
    }

    @Override
    public void stop() {

    }

    @Override
    public void run() {
        System.out.println("cannot run at this time");
    }
}

class CloseState extends LiftState {

    @Override
    public void open() {
        // do nothing

    }

    @Override
    public void close() {
        System.out.println("Lift is ready to close");

    }

    @Override
    public void stop() {
        System.out.println("Lift is ready to stop");
    }

    @Override
    public void run() {
        System.out.println("Lift is ready to run");
    }

}

class StopState extends LiftState {

    @Override
    public void open() {
        // TODO Auto-generated method stub
        System.out.println("Lift is ready to open");
    }

    @Override
    public void close() {
        // do nothing

    }

    @Override
    public void stop() {
        System.out.println("Lift is ready to stop");

    }

    @Override
    public void run() {
        System.out.println("Lift is ready to run");

    }

}

class RunningState extends LiftState {

    @Override
    public void open() {
        // do nothing
        System.out.println("Cannot open at this time");
    }

    @Override
    public void close() {
        // do nothing

    }

    @Override
    public void stop() {
        System.out.println("Lift is ready to stop");

    }

    @Override
    public void run() {
        System.out.println("Lift is ready to run");

    }

}

class Context {
    // define all states of a lift
    private LiftState state;

    public void setState(LiftState state) {
        this.state = state;
    }

    public void open() {
        state.open();
    }

    public void close() {
        state.close();
    }

    public void stop() {
        state.stop();
    }

    public void run() {
        state.run();
    }

}

public class StatePattern {

    public static void main(String[] args) {
        Context ctxt = new Context();
        ctxt.setState(new StopState());
        ctxt.open();
        ctxt.stop();
        ctxt.close();
        ctxt.run();
    }

}

其UML图如下:

可以看出来,状态模式的 Context 的作用就是与客户端进行交互,其内部也有所有的操作,然后内部的实现交给了各种状态对象。

状态模式避免了大量的if-else判断,将各种状态变为各种独立的类,这样就可以在每个单独的类中考虑问题。

可以看出来其与策略模式非常得相似,那么它们二者有什么区别呢?

对状态进行建模时,状态迁移是一个核心内容,比如上面的红绿灯的例子;然而,在选择策略时,迁移与此毫无关系。另外,策略模式允许一个客户选择或提供一种策略,而这种思想在状态模式中完全没有。

个人理解:状态模式重在状态的转换来模拟一个事件,而策略模式重在策略替换来模拟一个事件。

总结

状态模式的主要优点在于封装了转换规则,并枚举可能的状态,它将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为,还可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数;

其缺点在于使用状态模式会增加系统类和对象的个数,且状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切换状态的状态模式不满足“开闭原则”的要求。