状态模式——状态的内聚与解耦


Demo 地址: https://github.com/ooblee/HelloDesignPattern

1. 定义

状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。

现实的模式,比如人的情绪有状态,喜怒哀乐等等,不同的状态会随着某个事件而改变。

状态模式应用在这样的对象上面:

  • 有多种状态。
  • 不同的状态可以相互转换。
  • 不同的状态拥有不同的行为和数据。

状态模式把每一个状态进行封装,单独一个状态类,然后把和该状态的操作内聚到一起,以及处理下一个状态的切换。

所以对于状态节点,需要关心的是:

  • 如何对当前状态进行处理。
  • 如何切换到其他状态。

2. 设计

状态模式主要有这三种元素:

  • 环境类,对应有状态变化的对象,提供命令方法让外部来调用,进行状态的处理和切换。
  • 抽象状态类,定义了状态的处理方法。
  • 具体状态类,实现了状态的处理方法。

类图如下:

状态模式-类图

这里演示一个简单的模型。

假设我们的对象有三种状态:A,B 和 C。需要处理两个命令 cmd1 和 cmd2。对应的状态图为:

简单状态机

命令用枚举表示:

public enum Cmd {
    CMD_1("cmd1"),
    CMD_2("cmd2");

    final String value;

    Cmd(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

设计抽象状态类,处理命令的方法为 handle,因为需要转移外部环境的状态,或者外部环境的业务方法,这里要传入环境类。

public interface IState {

    void handle(Context context, Cmd cmd);
}

设计环境类,也就是需要状态变化的类,提供了三种方法

  • 初始化方法 init
  • 响应命令 cmd1
  • 响应命令 cmd2

初始化方法把状态初始化为起始状态 A。然后在处理命令的方法中,调用了当前状态的 handle 进行处理。

public class Context {

    private IState state;

    public void init() {
        state = new StateA();
    }

    public void switchState(IState state) {
        this.state = state;
    }

    public void cmd1() {
        state.handle(this, Cmd.CMD_1);
    }

    public void cmd2() {
        state.handle(this, Cmd.CMD_2);
    }
}

具体的状态类。

状态类 A,从状态图中可以看到, A 可以响应 cmd1 和 cmd2,所以对这两个命令都进行了处理。处理完毕后转移 Context 的状态。

public class StateA implements IState {

    public void handle(Context context, Cmd cmd) {
        switch (cmd) {
            case CMD_1:
                handleCmd1(context);
                break;
            case CMD_2:
                handleCmd2(context);
                break;
            default:
                System.out.println("State A 不处理 " + cmd.getValue());
                break;
        }
    }

    private void handleCmd1(Context context) {
        System.out.println("State A 处理 cmd1");
        context.switchState(new StateB());
        System.out.println("A => B");
    }

    private void handleCmd2(Context context) {
        System.out.println("State A 处理 cmd2");
        context.switchState(new StateC());
        System.out.println("A => C");
    }
}

状态 B 只关心命令 cmd1,需要处理 cmd1:

public class StateB implements IState {

    public void handle(Context context, Cmd cmd) {
        switch (cmd) {
            case CMD_1:
                handleCmd1(context);
                break;
            default:
                System.out.println("State B 不处理 " + cmd.getValue());
                break;
        }
    }

    private void handleCmd1(Context context) {
        System.out.println("State B 处理 cmd1");
        context.switchState(new StateC());
        System.out.println("B => C");
    }
}

状态 C 只关心命令 cmd2,需要处理 cmd2:

public class StateC implements IState {

    public void handle(Context context, Cmd cmd) {
        switch (cmd) {
            case CMD_2:
                handleCmd2(context);
                break;
            default:
                System.out.println("State C 不处理 " + cmd.getValue());
                break;
        }
    }

    private void handleCmd2(Context context) {
        System.out.println("State C 处理 cmd2");
        context.switchState(new StateB());
        System.out.println("C => B");
    }
}

使用者,只需要创建一个环境类,调用初始化方法,然后任意发送命令。环境类就按照状态机正常地运转起来,无效的命令直接略过。

public class TestState {

    public static void main(String[] args) {

        // 初始化环境
        Context context = new Context();
        context.init();

        // 处理命令
        context.cmd1();
        context.cmd1();
        context.cmd2();
        context.cmd2();
        context.cmd1();
        context.cmd2();
        context.cmd2();
        context.cmd2();
        context.cmd2();
        context.cmd2();
    }
}

显示:

State A 处理 cmd1
A => B
State B 处理 cmd1
B => C
State C 处理 cmd2
C => B
State B 不处理 cmd2
State B 处理 cmd1
B => C
State C 处理 cmd2
C => B
State B 不处理 cmd2
State B 不处理 cmd2
State B 不处理 cmd2
State B 不处理 cmd2

后续的修改也很方便:

  • 假如 A 状态要修改,只需要修改 StateA,不影响其他状态类。
  • 新增状态,新增一个状态类,然后修改一下状态的切换方式,不影响其他的状态类。

3. 应用

一个对象内部状态变化,最简单的做法直接用 if else 条件选择,然后在选择代码块中进行处理。

当内部状态切换复杂的情况下,会产生大量的 if else 条件选择,修改不方便,可读性也大幅度降低。

这时候可以使用状态模式进行优化。这里它类似于策略模式,把内部各个状态需要处理的业务独立且内聚到一个类中,然后只需要关心当前状态的业务处理。

状态存在于内存,适用客户端的场景。

服务端开发中,需要对状态进行存储,可以选用数据库进行存储。使用状态模式可以实现一个简单的工作流引擎。

4. 特点

4.1. 优势

  • 易修改:取代多重条件选择,修改状态不需要修改多个地方,只要修改当前状态的代码。
  • 易扩展:新增状态创建新的状态类,新增到其他状态的切换代码。
  • 易切换:状态相关代码内部到状态类中,每次转换只需要关心当前状态到其他状态的切换。
  • 节约内存:状态类可以做成单例,全局共享。

4.2. 缺点

  • 类数量膨胀。如果状态很多的话,意味着状态类也会相应增加。
  • 系统变复杂。需要对着状态图看才容易理解整个流程。
发布了61 篇原创文章 · 获赞 43 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/firefile/article/details/90314204