设计模式(十):状态模式(State Pattern)

状态这个词较好理解,生活中很多事物都会有状态,有不同的状态,状态是可转变迁移的。例如 人有精神状态、身体会有疲劳状态或打鸡血状态。

在系统开发中,程序中有些对象可能会根据不同的状态做出不同的行为,把这种对象称为 有状态(stateful)的对象,把影响对象行为的一个或多个动态变化的属性称为 状态

上一篇讲了 策略模式,紧接着这下一篇就讲状态模式,两者都是行为模式,并且容易混淆,注意它们的区别。

策略模式:是对一系列算法分别封装,并可互相替换且不影响客户,将算法使用与实现分离。

状态模式:是对象根据它内部的状态(属性)迁移,而改变自身的行为。

模式定义

在状态模式中,对象的行为是依赖于它的状态(属性)。当对象与外部交互时,触发其内部状态迁移,从而使得对象的行为也随之发生改变,状态模式又称为状态机模式。这种类型的设计模式属于 行为型模式

传统解决状态迁移而改变行为的解决方案,通常是将所有可能的状态考虑到,然后用 if...else....switch...case...对状态逐个判断,再进行不同情况的处理。但状态很多时,程序则变得很复杂,且增加状态时要添加新的判断,违背了 开闭原则,不利于扩展和维护。

状态模式可以很好地解决上面问题,可以用于消除多层次复杂的条件选择语句。

模式分析

状态模式的关键是引入了一个抽象类来专门表示对象的状态,这个类叫做 抽象状态类,而对象的每一种具体状态都继承该类,在重写的方法里实现自己状态的行为,包括各种状态迁移。

状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关 判断逻辑 提取出来,放到一系列的状态类当中,这样可以把原来复杂的逻辑判断简单化。

状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为。

模式结构

状态模式包含的主要角色:

  1. 抽象状态(State):抽象状态,用于封装环境对象(Context)中特定状态所对应的行为。可以是抽象类,也可以是接口,具体状态类继承这个父类。

  2. 具体状态(Concrete State):具体状态,实现抽象状态类中的方法,方法里封装自己状态的行为。

  3. 环境(Context):持有一个抽象状态类型的属性用于维护当前状态,定义一个方法,在方法里将与状态相关的操作委托托给当前状态对象来处理。

    环境类实际上就是拥有状态的对象,有时可以充当 状态管理器(State Manager) 的角色,可以在环境中对状态进行切换操作。

优缺点

  1. 优点
    • 在具体状态对象类里封装状态对应的逻辑。
    • 扩展性强,易于添加新的状态对象或行为。
  2. 缺点
    • 状态过多可能导致类膨胀,结构与实现都较为复杂,使用不当可能导致程序结构和代码的混乱。
    • 以 开闭原则 的支持并不态好,当新增状态时,需要修改那些负责状态转换的源码,否则无法切换到新增的状态

应用场景

通常在以下情况下可以考虑使用状态模式:

  • 对象行为依赖于它的状态(属性),并且状态在运行时可动态改变,从而改变它的行为,可以考虑使用状态模式。
  • 代码中包含大量与对象状态有关的条件语句,这些条件语句中包含了对象的行为,这些条件对应于各种状态,可以考虑使用状态模式。

与策略模式

  • 状态模式:状态可变,从而改变行为,即改变操作流程。
  • 策略模式:算法可选,从而选择对应的策略。

模式应用

例如 OA 办公系统,一个批文有多种状态,状态不同时对应的操作也不同。使用状态模式可以描述工作流对象的状态转换及不同状态下它所具有的行为。

典型应用

Spring 提供了一个 Spring Statemachine 框架,供开发人员在 Spring 应用中使用状态机概念。

代码示例

  1. 抽象状态接口,定义状态的处理行为方法

    1
    2
    3
    public interface IState {
    void handle(Context context);
    }
  2. 具体状态类,实现抽象状态接口中的行为方法

    注意:当前状态业务处理完后,设置下一个状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class ConcreteStateA implements IState {

    @Override
    public void handle(Context context) {
    System.out.println("当前状态:A");
    //状态 A 执行完后,迁移到状态 B
    context.setState(new ConcreteStateB());
    }
    }

    public class ConcreteStateB implements IState {

    @Override
    public void handle(Context context) {
    System.out.println("当前状态:B");
    //状态 B 执行完后,迁移到状态 A
    context.setState(new ConcreteStateA());
    }
    }
  3. 环境 Context,持有抽象状态类型属性,定义一个使用状态对象属性调用其行为的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class Context {

    private IState state;

    public Context() {

    }

    public Context(IState state) {
    this.state = state;
    }

    void handle() {
    // 注意:这里传入当前对象
    state.handle(this);
    }

    public IState getState() {
    return state;
    }

    public Context setState(IState state) {
    this.state = state;
    return this;
    }
    }
  4. 客户端调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class StateMain {

    public static void main(String[] args) {
    Context context = new Context(new ConcreteStateA());
    context.handle();
    context.handle();
    context.handle();
    context.handle();
    context.handle();
    }
    }

    输出结果:

    1
    2
    3
    4
    5
    当前状态:A
    当前状态:B
    当前状态:A
    当前状态:B
    当前状态:A
  5. 结果分析:

    从上面示例中可以看出,客户端给 环境对象 Context 传入了一个初始值,context 的 handle() 方法内部是 抽象状态类型属性调用状态处理方法。

    当前状态处理完后,需要设置下一个状态,实现了状态迁移,再次调用 handle() 方法时,自动进入下一个状态的处理。

相关参考

  1. RUNOOB-状态模式
  2. 状态模式(详解版)
  3. 图说设计模式-状态模式
  4. 设计模式之状态模式
  5. 实际项目运用之State模式

设计模式(十):状态模式(State Pattern)

http://blog.gxitsky.com/2019/10/20/DesignPatterns-10-State/

作者

光星

发布于

2019-10-20

更新于

2022-06-17

许可协议

评论