18. 状态模式(State)

问题:对象有大量状态判断逻辑(长 switch),状态间转换复杂。

核心:把每个状态封装成独立类,状态切换就是切换当前状态对象。

interface State {
  publish(): void;
  reject(): void;
}

class Draft implements State {
  constructor(private doc: ArticleDocument) {}
  publish(): void {
    console.log('提交审核');
    this.doc.setState(new Moderation(this.doc));
  }
  reject(): void { console.log('草稿不能被拒绝'); }
}

class Moderation implements State {
  constructor(private doc: ArticleDocument) {}
  publish(): void {
    console.log('审核通过,已发布');
    this.doc.setState(new Published(this.doc));
  }
  reject(): void {
    console.log('审核未通过,退回草稿');
    this.doc.setState(new Draft(this.doc));
  }
}

class Published implements State {
  constructor(private doc: ArticleDocument) {}
  publish(): void { console.log('已发布,无需重复操作'); }
  reject(): void {
    console.log('撤回发布');
    this.doc.setState(new Draft(this.doc));
  }
}

class ArticleDocument {
  #state: State;
  constructor() { this.#state = new Draft(this); }
  setState(s: State): void { this.#state = s; }
  publish(): void { this.#state.publish(); }
  reject(): void { this.#state.reject(); }
}

const doc = new ArticleDocument();
doc.publish(); // 提交审核
doc.publish(); // 审核通过,已发布
doc.reject();  // 撤回发布
package state

import "fmt"

type Document struct {
  state State
}

func (d *Document) SetState(s State) { d.state = s }
func (d *Document) Publish()         { d.state.Publish() }
func (d *Document) Reject()          { d.state.Reject() }

type State interface {
  Publish()
  Reject()
}

type Draft struct{ doc *Document }

func (s Draft) Publish() {
  fmt.Println("提交审核")
  s.doc.SetState(Moderation{doc: s.doc})
}

func (s Draft) Reject() { fmt.Println("草稿不能被拒绝") }

type Moderation struct{ doc *Document }

func (s Moderation) Publish() {
  fmt.Println("审核通过,已发布")
  s.doc.SetState(Published{doc: s.doc})
}

func (s Moderation) Reject() {
  fmt.Println("审核未通过,退回草稿")
  s.doc.SetState(Draft{doc: s.doc})
}

type Published struct{ doc *Document }

func (s Published) Publish() { fmt.Println("已发布,无需重复操作") }

func (s Published) Reject() {
  fmt.Println("撤回发布")
  s.doc.SetState(Draft{doc: s.doc})
}

实战场景

  • 订单生命周期:待支付、已支付、已发货、已完成、已取消,每个状态允许的操作不同。
  • 工作流审批:草稿、审核中、已通过、已驳回、已撤回之间的转换规则复杂,适合封装成状态对象。
  • 播放器控制:停止、播放、暂停、缓冲、错误状态下,同一个按钮点击行为不同。
  • 登录会话:未登录、已登录、过期、被封禁、多因素认证中,各状态决定可访问能力。
  • WebSocket 连接:connecting、open、closing、closed、reconnecting 下的发送和重连逻辑不同。
  • 游戏角色行为:站立、奔跑、跳跃、攻击、受伤、死亡各自响应输入和碰撞。
  • 表单提交流程:编辑中、校验中、提交中、成功、失败状态下按钮、提示和重试行为不同。
  • 文档发布:草稿、待审核、已发布、归档、撤回,每个状态限定可执行动作。
  • 设备状态:离线、在线、忙碌、故障、维护中,控制命令和告警规则都不同。
  • 任务调度:等待、运行中、重试中、成功、失败、取消,不同状态决定是否能重试或终止。