Menu
CoddyTech

状态模式

本课属于 Coddy Java 学习旅程的“面向对象编程”单元。第 74 课,共 87 课。

状态模式是一种行为型设计模式,它允许对象在内部状态发生变化时改变行为。对象看起来像是改变了自己的类,但实际上,它将行为委托给不同的状态对象。想象一台自动售货机:它对按下按钮的响应取决于它是处于空闲状态、已投入钱币,还是正在出货。

你不再使用复杂的条件语句来处理不同状态,而是将每种状态封装在自己的类中。该模式包含一个用于保存当前状态引用的Context,以及为每种状态定义行为的State类:

interface State {
    void handle(Context context);
}

class Context {
    private State state;
    
    public void setState(State state) {
        this.state = state;
    }
    
    public void request() {
        state.handle(this);
    }
}

每个具体状态都会实现相应的行为,并可以触发转换到其他状态:

class IdleState implements State {
    public void handle(Context context) {
        System.out.println("Idle: Insert coin to start");
        context.setState(new ActiveState());
    }
}

class ActiveState implements State {
    public void handle(Context context) {
        System.out.println("Active: Processing...");
        context.setState(new IdleState());
    }
}

上下文会将请求委托给当前状态,并随着状态的变化自动改变行为:

Context context = new Context();
context.setState(new IdleState());

context.request();  // 空闲:投入硬币即可开始
context.request();  // 活动中:正在处理……

当对象的行为高度依赖其状态,并且必须在运行时改变时,状态模式是理想的选择。它能消除大型条件代码块,让添加新状态变得简单,只需创建一个实现 State 接口的新类即可。

challenge icon

挑战

简单

让我们使用状态模式构建一个文档工作流系统!你将创建一个系统,用来模拟文档如何经历不同阶段:Draft、Review 和 Published。每种状态都会决定可以执行哪些操作,以及尝试编辑或批准文档时文档会如何表现。

你将把代码分到四个文件中:

  • DocumentState.java:定义所有状态类都要实现的 DocumentState 接口。它应声明两个方法:edit(Document doc) 用于尝试编辑文档,approve(Document doc) 用于尝试批准文档或将文档推进到下一阶段。
  • States.java:创建三个实现 DocumentState 的具体状态类:

    DraftState:调用 edit 时,打印 Editing draft...。调用 approve 时,打印 Draft approved. Moving to review.,并将文档转换为 ReviewState。

    ReviewState:调用 edit 时,打印 Cannot edit during review.(状态不变)。调用 approve 时,打印 Review complete. Publishing document.,并转换为 PublishedState。

    PublishedState:调用 edit 时,打印 Cannot edit published document.。调用 approve 时,打印 Document already published.。这两个操作都不会改变状态。

  • Document.java:创建用于维护当前状态的上下文类。你的 Document 应包含:

    一个保存当前 DocumentState 的私有字段,并在构造函数中将其初始化为 DraftState。

    一个用于更改当前状态的 setState(DocumentState state) 方法。

    一个将调用委托给当前状态的 edit 方法的 edit() 方法。

    一个将调用委托给当前状态的 approve 方法的 approve() 方法。

    一个返回当前状态的简单类名的 getStatus() 方法(使用 getClass().getSimpleName())。

  • Main.java:演示你的状态系统!你会收到一个输入:由逗号分隔的操作字符串,其中每个操作都是 edit 或 approve(例如:edit,approve,edit,approve)。

    创建一个 Document,并按 Status: [state] 的格式打印它的初始状态。然后按顺序处理每个操作,在文档上调用相应的方法。每个操作执行后,打印当前状态。

你会收到一个输入:由逗号分隔的操作字符串。

例如,输入为 edit,approve,approve 时,输出为:

Status: DraftState
Editing draft...
Status: DraftState
Draft approved. Moving to review.
Status: ReviewState
Review complete. Publishing document.
Status: PublishedState

请注意,同一个 approve() 调用会根据文档的当前状态产生不同结果。这正是状态模式的核心!文档不会使用条件语句来检查自身状态,而是将行为委托给当前状态对象,由它恰当地处理操作,并在需要时触发状态转换。

亲手试试

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.nextLine();
        
        // TODO:创建一个新的 Document 对象
        
        // TODO:按 "Status: [state]" 格式输出初始状态
        
        // TODO:用逗号分割输入,得到各个操作
        
        // TODO:循环处理每个操作
        // 如果操作是 "edit",就对 document 调用 edit()
        // 如果操作是 "approve",就对 document 调用 approve()
        // 每次操作后,输出当前状态
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Java 在线编译器