设计模式-状态模式

状态模式

状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

核心思想:

将与特定状态相关的行为局部化,并且将不同状态的行为分割开来,封装到不同的状态类中。Context 对象(即需要根据状态改变行为的对象)将与状态相关的操作委托给当前的状态对象。当 Context 对象的状态改变时,它会改变其持有的状态对象的引用。

解决的问题:

当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变行为时,通常会在对象内部使用大量的条件语句(if/else 或 switch)。状态模式可以将这些分支逻辑分散到不同的状态类中,从而使 Context 类更加简洁,并且更容易添加新的状态和行为。

主要角色:

  1. Context (环境类/上下文类)

    • 定义了客户端感兴趣的接口。

    • 维护一个 State 对象的实例,这个实例定义了对象的当前状态。

    • 将所有与状态相关的请求委托给当前的 State 对象。

    • 通常还提供一个方法来改变当前状态。

  2. State (状态接口/抽象状态类)

    • 定义了一个接口,封装与 Context 的一个特定状态相关的行为。

    • 通常包含一组方法,对应 Context 可能执行的操作。

  3. ConcreteState (具体状态类)

    • 实现了 State 接口。

    • 每一个具体状态类都实现了属于特定状态的行为。

    • 在处理完某个请求后,具体状态类通常会决定 Context 的下一个状态(即状态转换)。

图示 (简化版 UML 思路):

复制代码
+-----------------+       has a       +-----------+
|     Context     |------------------>|   State   |
|-----------------|                   |-----------|
| - currentState  |                   | + handle()|
|-----------------|                   +-----------+
| + request()     |                         ^
| + setState()    |                         | (implements)
+-----------------+                         |
      | delegates to current state          |
      +-------------------------------------+
                                         /|\
                                          |
                   +----------------------+----------------------+
                   |                                              |
        +-------------------+                         +-------------------+
        | ConcreteStateA    |                         | ConcreteStateB    |
        |-------------------|                         |-------------------|
        | + handle()        |<- (may transition to) ->| + handle()        |
        +-------------------+                         +-------------------+

一个生活中的例子:自动售货机

一个自动售货机有多种状态:

  • NoCoinState (没有投币状态)

  • HasCoinState (已投币状态)

  • SoldState (商品售出状态)

  • SoldOutState (商品售罄状态)

根据当前状态,售货机对用户的操作(投币、按按钮、退币)会有不同的反应:

  • 没有投币状态

    • 投币 -> 转换到 HasCoinState。

    • 按按钮 ->提示请先投币。

  • 已投币状态

    • 投币 -> 提示已投币。

    • 按按钮 -> 检查是否有货,有货则转换到 SoldState 并出货,无货则提示并转换到 SoldOutState(如果刚好卖完)或保持 HasCoinState 并提示用户选择其他商品。

    • 退币 -> 退还硬币,转换到 NoCoinState。

  • 商品售出状态

    • (出货动作完成后)如果还有货 -> 转换到 NoCoinState。

    • (出货动作完成后)如果没货了 -> 转换到 SoldOutState。

    • 其他操作 -> 提示正在出货。

  • 商品售罄状态

    • 投币 -> 退币,提示已售罄。

    • 按按钮 -> 提示已售罄。

优点:

  1. 将与特定状态相关的行为局部化,并且将不同状态的行为分割开来:每个状态的逻辑都封装在自己的类中,使得代码更加清晰、易于理解和维护。

  2. 使得状态转换更加明确:状态转换的逻辑可以放在状态类内部或者 Context 类内部,使得状态之间的切换清晰可见。

  3. 易于增加新的状态和转换:增加一个新的状态只需要增加一个新的具体状态类,并修改相关的状态转换逻辑,符合开闭原则(对扩展开放,对修改关闭)。

  4. 消除了庞大的条件分支语句:将 if/else 或 switch 语句转化为状态对象的不同实现,使得 Context 类更加简洁。

缺点:

  1. 类S数量增多:状态模式会增加系统中类和对象的个数,如果状态非常多,会导致类爆炸。

  2. 模式结构对初学者可能显得复杂:如果状态转换逻辑非常简单,或者状态数量很少,使用状态模式可能会显得小题大做。

何时使用状态模式?

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

  • 代码中包含大量与对象状态有关的条件语句。

  • 当一个操作中含有庞大的多分支条件语句,且这些分支依赖于该对象的状态时。

Java 示例 (简化的文档审批流程):

假设我们有一个文档对象,它有以下几种状态:

  • DraftState (草稿状态)

  • ModerationState (审核中状态)

  • PublishedState (已发布状态)

复制代码
// 1. State (状态接口)
interface DocumentState {
    void handleSubmit(DocumentContext document); // 提交审批
    void handleApprove(DocumentContext document); // 审批通过
    void handleReject(DocumentContext document);  // 审批拒绝
    void handlePublish(DocumentContext document); // 发布
    String getStateName();
}
​
// 2. ConcreteState (具体状态类)
class DraftState implements DocumentState {
    @Override
    public void handleSubmit(DocumentContext document) {
        System.out.println("Document submitted for moderation.");
        document.setState(new ModerationState()); // 状态转换
    }
​
    @Override
    public void handleApprove(DocumentContext document) {
        System.out.println("Error: Document is in draft state, cannot approve directly.");
    }
​
    @Override
    public void handleReject(DocumentContext document) {
        System.out.println("Error: Document is in draft state, cannot reject.");
    }
​
    @Override
    public void handlePublish(DocumentContext document) {
        System.out.println("Error: Document must be approved before publishing.");
    }
​
    @Override
    public String getStateName() {
        return "Draft";
    }
}
​
class ModerationState implements DocumentState {
    @Override
    public void handleSubmit(DocumentContext document) {
        System.out.println("Error: Document is already under moderation.");
    }
​
    @Override
    public void handleApprove(DocumentContext document) {
        System.out.println("Document approved by moderator.");
        // 假设审批通过后直接可以发布 (或者可以有更复杂的状态,如 ApprovedNotPublishedState)
        // 为了简化,我们这里直接设置为可以发布,但还未发布
        // 实践中,可能需要一个 ApprovedState,然后由该状态处理发布请求
        // 或者,审批通过后自动发布(如果业务是这样)
        // 这里我们让它变成一个“准备发布”的状态,等待发布指令
        document.setState(new ApprovedState()); // 假设有一个ApprovedState
    }
​
    @Override
    public void handleReject(DocumentContext document) {
        System.out.println("Document rejected by moderator. Reverted to draft.");
        document.setState(new DraftState()); // 状态转换
    }
​
    @Override
    public void handlePublish(DocumentContext document) {
        System.out.println("Error: Document is under moderation, cannot publish yet.");
    }
​
    @Override
    public String getStateName() {
        return "Moderation";
    }
}
​
// 新增一个审批通过但未发布的状态
class ApprovedState implements DocumentState {
    @Override
    public void handleSubmit(DocumentContext document) {
        System.out.println("Error: Document is already approved.");
    }
​
    @Override
    public void handleApprove(DocumentContext document) {
        System.out.println("Error: Document is already approved.");
    }
​
    @Override
    public void handleReject(DocumentContext document) {
        System.out.println("Document approval rescinded. Reverted to draft.");
        document.setState(new DraftState());
    }
​
    @Override
    public void handlePublish(DocumentContext document) {
        System.out.println("Document published successfully.");
        document.setState(new PublishedState()); // 状态转换
    }
    @Override
    public String getStateName() {
        return "Approved (Ready to Publish)";
    }
}
​
​
class PublishedState implements DocumentState {
    @Override
    public void handleSubmit(DocumentContext document) {
        System.out.println("Error: Document is already published. Cannot submit again.");
    }
​
    @Override
    public void handleApprove(DocumentContext document) {
        System.out.println("Error: Document is already published.");
    }
​
    @Override
    public void handleReject(DocumentContext document) {
        System.out.println("Error: Document is already published.");
    }
​
    @Override
    public void handlePublish(DocumentContext document) {
        System.out.println("Error: Document is already published.");
    }
    @Override
    public String getStateName() {
        return "Published";
    }
}
​
​
// 3. Context (环境类)
class DocumentContext {
    private DocumentState currentState;
    private String content;
​
    public DocumentContext(String content) {
        this.content = content;
        this.currentState = new DraftState(); // 初始状态为草稿
        System.out.println("Document created. Initial state: " + currentState.getStateName());
    }
​
    public void setState(DocumentState state) {
        this.currentState = state;
        System.out.println("Document state changed to: " + this.currentState.getStateName());
    }
​
    public DocumentState getCurrentState() {
        return currentState;
    }
​
    public String getContent() {
        return content;
    }
​
    public void setContent(String content) {
        this.content = content;
        System.out.println("Document content updated.");
    }
​
    // 将请求委托给当前状态对象
    public void submit() {
        currentState.handleSubmit(this);
    }
​
    public void approve() {
        currentState.handleApprove(this);
    }
​
    public void reject() {
        currentState.handleReject(this);
    }
​
    public void publish() {
        currentState.handlePublish(this);
    }
}
​
// 客户端代码
public class StatePatternDemo {
    public static void main(String[] args) {
        DocumentContext doc = new DocumentContext("My awesome article content.");
        System.out.println("Current Document State: " + doc.getCurrentState().getStateName());
​
        System.out.println("\n--- Trying to publish draft ---");
        doc.publish(); // Error: Document must be approved before publishing.
​
        System.out.println("\n--- Submitting document ---");
        doc.submit(); // Document submitted for moderation. State changed to: Moderation
​
        System.out.println("\n--- Trying to submit again ---");
        doc.submit(); // Error: Document is already under moderation.
​
        System.out.println("\n--- Moderator rejects ---");
        doc.reject(); // Document rejected by moderator. Reverted to draft. State changed to: Draft
​
        System.out.println("\n--- User edits and submits again ---");
        doc.setContent("My awesome article content - v2");
        doc.submit(); // Document submitted for moderation. State changed to: Moderation
​
        System.out.println("\n--- Moderator approves ---");
        doc.approve(); // Document approved by moderator. State changed to: Approved (Ready to Publish)
​
        System.out.println("\n--- Trying to approve again ---");
        doc.approve(); // Error: Document is already approved.
​
        System.out.println("\n--- Publishing document ---");
        doc.publish(); // Document published successfully. State changed to: Published
​
        System.out.println("\n--- Trying to publish again ---");
        doc.publish(); // Error: Document is already published.
    }
}

在这个例子中:

  • DocumentContext 是环境类,它维护文档的当前状态。

  • DocumentState 是状态接口,定义了不同状态下可以执行的操作。

  • DraftState, ModerationState, ApprovedState, PublishedState 是具体状态类,它们实现了特定状态下的行为和状态转换逻辑。

  • 客户端通过调用 DocumentContext 的方法(如 submit(), approve())来与文档交互,而 DocumentContext 会将这些请求委托给其当前的 DocumentState 对象。

状态模式使得我们可以很容易地添加新的状态(比如 ArchivedState 归档状态)而不需要修改 DocumentContext 类,只需要创建新的状态类并调整状态转换即可。

相关推荐
天天进步20153 小时前
UI-TARS 源码解析 #10:parse_action 源码解析:如何用 AST 安全解析模型生成的函数调用?
安全·ui
FuckPatience12 小时前
Telerik UI for WPF 值不能为null。参数名:key
ui·wpf
breeze jiang13 小时前
从原生事件到 React 组件化:用状态驱动模型加载进度 UI
前端·react.js·ui
杨充14 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充14 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充14 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充14 小时前
2.面向对象的特性
设计模式
杨充14 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充14 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充14 小时前
3.接口vs抽象类比较
设计模式