Java - GoF设计模式详解21(状态模式)
二十一、状态模式
1,基本介绍
(1)状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,这个对象看起来像是改变了其类。
(2)该模式中包含的角色及其职责如下:
- 环境角色(Context):也叫作上下文,它定义了客户需要的接口,内部维护一个当前的状态,并负责具体状态的切换。
- 抽象状态角色(Abstract State):定义了一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态(Concrete State):实现抽象状态所定义的行为,并且在需要的情况下进行状态切换。
2,使用样例
(1)下面是一个关于打印机的状态模式示例。首先我们一个打印机状态接口(抽象状态),里面声明一个 print() 方法:
// 打印机状态接口 interface State { // 开始打印 void print(); }
(2)接着定义了两种具体状态:可以打印的状态和缺纸的状态。
- 当打印机处于可以打印的状态时,调用 print() 方法会打印“正在打印...”,并将打印机的状态切换到缺纸的状态。
- 当打印机处于缺纸的状态时,调用 print() 方法会打印“缺纸,无法打印。”。
// 可以打印的状态 class CanPrintState implements State { // 打印机对象 private Printer printer; public CanPrintState(Printer printer) { this.printer = printer; } @Override public void print() { System.out.println("正在打印..."); // 切换状态 printer.setState(printer.getOutOfPaperState()); } } // 缺纸的状态 class OutOfPaperState implements State { // 打印机对象 private Printer printer; public OutOfPaperState(Printer printer) { this.printer = printer; } @Override public void print() { System.out.println("缺纸,无法打印。"); } }
(3)然后定义一个打印机类(环境角色),内部维护一个当前的状态,并负责具体状态的切换。
// 打印机 class Printer { // 可以打印的状态 private State canPrintState; // 缺纸的状态 private State outOfPaperState; // 当前的状态 private State state; public Printer() { // 初始化状态 canPrintState = new CanPrintState(this); outOfPaperState = new OutOfPaperState(this); // 设置当前状态为可以打印的状态 state = canPrintState; } // 打印 public void print() { state.print(); } // 设置状态 public void setState(State state) { this.state = state; } public State getCanPrintState() { return canPrintState; } public State getOutOfPaperState() { return outOfPaperState; } }
(4)最后测试一下,我们们创建了一个 Printer 对象,并调用了它的 print() 方法。由于打印机的初始状态是可以打印的,所以在第一次调用 print() 方法时会打印“正在打印...”,并将打印机的状态切换到缺纸的状态。接着,当我们再次调用 print() 方法时,由于打印机的状态已经是缺纸,所以会打印“缺纸,无法打印。”。
public class Test { public static void main(String[] args) { Printer printer = new Printer(); printer.print(); printer.print(); } }
附一:JDK 中的状态模式
(1)在 JDK 中的 java.util.Iterator 接口使用了状态模式。java.util.Iterator 中 的 hasNext 和 next 方法就是根据迭代器的内部状态来决定如何表现的。
- 例如,当迭代器处于“已遍历完集合中的所有元素”的状态时,调用 next 方法将抛出一个 NoSuchElementException 异常。
(2)java.util.Iterator 是一个接口,它包含了两个方法:hasNext 和 next。这两个方法的实现是由迭代器的具体实现类来决定的。这些具体实现类可以是某个集合的内部类,也可以是一个单独的类。
- hasNext 方法返回一个布尔值,表示迭代器是否还有下一个元素可供遍历。
- next 方法返回迭代器的下一个元素。
(3)在这些具体实现类中,迭代器会维护一些内部状态,比如当前遍历到的位置、是否已遍历完集合中的所有元素等。根据这些状态,迭代器的 hasNext 和 next 方法就会做出不同的反应。
例如,在 java.util.ArrayList 的内部类 Itr 中,迭代器维护了一个 cursor 变量,表示当前遍历到的位置。
- 在调用 hasNext 方法时,如果 cursor 小于 size(集合的大小),则说明还有下一个元素可供遍历,因此返回 true;如果 cursor 等于 size,则说明已遍历完集合中的所有元素,因此返回 false。
- 在调用 next 方法时,如果 cursor 小于 size,则返回下一个元素,并将 cursor 加一;如果 cursor 等于 size,则说明已遍历完集合中的所有元素,此时调用 next 方法就会抛出一个 NoSuchElementException 异常。
- Context(上下文):这里的 Context 就是迭代器本身,也就是具体实现类。它维护了一些内部状态,并提供了 hasNext 和 next 方法来遍历集合中的元素。
- State(状态):在这个示例中,状态就是迭代器的内部状态,比如当前遍历到的位置、是否已遍历完集合中的所有元素等。
- ConcreteState(具体状态):在这个示例中,就是迭代器的两种内部状态:“有下一个元素可供遍历”和“已遍历完集合中的所有元素”。
附二:Spring 中的状态模式
1,Spring Statemachine
(1)Spring Statemachine 是一个基于状态模式的状态机框架。它提供了一种简单而灵活的方法来管理状态,转移和事件。
(2)在 Spring Statemachine 中,状态机的状态和转移是由状态实例和转移实例表示的。状态机的行为由状态实例和转移实例之间的关系定义,而不是由状态机的实现细节定义。这使得状态机的行为可以在运行时更改,并且可以在不同的场景中重用。
关于 Spring Statemachine 更详细的介绍可以参考我之前写的文章: