咖啡机状态管理

用状态机模式实现咖啡机的状态流转

问题

设计一个咖啡机类,实现以下功能:

  • 咖啡机有多个状态:空闲、加水、加咖啡豆、冲泡、完成
  • 状态之间有明确的流转规则
  • 非法的状态转换要给出提示

解答

基础版:使用状态机模式

class CoffeeMachine {
  // 定义所有状态
  static STATES = {
    IDLE: 'idle',           // 空闲
    WATER_ADDED: 'water',   // 已加水
    BEANS_ADDED: 'beans',   // 已加咖啡豆
    BREWING: 'brewing',     // 冲泡中
    DONE: 'done'            // 完成
  }

  // 定义状态转换规则
  static TRANSITIONS = {
    idle: ['water'],
    water: ['beans'],
    beans: ['brewing'],
    brewing: ['done'],
    done: ['idle']
  }

  constructor() {
    this.state = CoffeeMachine.STATES.IDLE
  }

  // 检查是否可以转换到目标状态
  canTransitionTo(nextState) {
    const allowedStates = CoffeeMachine.TRANSITIONS[this.state]
    return allowedStates.includes(nextState)
  }

  // 执行状态转换
  transitionTo(nextState) {
    if (!this.canTransitionTo(nextState)) {
      console.log(`无法从 ${this.state} 转换到 ${nextState}`)
      return false
    }
    this.state = nextState
    console.log(`状态变更为: ${this.state}`)
    return true
  }

  // 加水
  addWater() {
    return this.transitionTo(CoffeeMachine.STATES.WATER_ADDED)
  }

  // 加咖啡豆
  addBeans() {
    return this.transitionTo(CoffeeMachine.STATES.BEANS_ADDED)
  }

  // 开始冲泡
  brew() {
    return this.transitionTo(CoffeeMachine.STATES.BREWING)
  }

  // 完成
  finish() {
    return this.transitionTo(CoffeeMachine.STATES.DONE)
  }

  // 重置
  reset() {
    return this.transitionTo(CoffeeMachine.STATES.IDLE)
  }
}

// 测试
const machine = new CoffeeMachine()
machine.addWater()   // 状态变更为: water
machine.addBeans()   // 状态变更为: beans
machine.brew()       // 状态变更为: brewing
machine.addWater()   // 无法从 brewing 转换到 water
machine.finish()     // 状态变更为: done
machine.reset()      // 状态变更为: idle

进阶版:支持异步操作和事件监听

class CoffeeMachine {
  static STATES = {
    IDLE: 'idle',
    WATER_ADDED: 'water',
    BEANS_ADDED: 'beans',
    BREWING: 'brewing',
    DONE: 'done'
  }

  static TRANSITIONS = {
    idle: ['water'],
    water: ['beans'],
    beans: ['brewing'],
    brewing: ['done'],
    done: ['idle']
  }

  constructor() {
    this.state = CoffeeMachine.STATES.IDLE
    this.listeners = new Map()
  }

  // 订阅状态变化
  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, [])
    }
    this.listeners.get(event).push(callback)
    return () => this.off(event, callback) // 返回取消订阅函数
  }

  // 取消订阅
  off(event, callback) {
    const callbacks = this.listeners.get(event)
    if (callbacks) {
      const index = callbacks.indexOf(callback)
      if (index > -1) callbacks.splice(index, 1)
    }
  }

  // 触发事件
  emit(event, data) {
    const callbacks = this.listeners.get(event) || []
    callbacks.forEach(cb => cb(data))
  }

  // 状态转换
  async transitionTo(nextState) {
    if (!CoffeeMachine.TRANSITIONS[this.state]?.includes(nextState)) {
      throw new Error(`无法从 ${this.state} 转换到 ${nextState}`)
    }
    
    const prevState = this.state
    this.state = nextState
    this.emit('stateChange', { from: prevState, to: nextState })
    
    return this.state
  }

  // 模拟冲泡过程(异步)
  async brew() {
    await this.transitionTo(CoffeeMachine.STATES.BREWING)
    
    // 模拟冲泡耗时
    await new Promise(resolve => setTimeout(resolve, 2000))
    
    await this.transitionTo(CoffeeMachine.STATES.DONE)
    this.emit('coffeeReady')
  }

  async addWater() {
    return this.transitionTo(CoffeeMachine.STATES.WATER_ADDED)
  }

  async addBeans() {
    return this.transitionTo(CoffeeMachine.STATES.BEANS_ADDED)
  }

  async reset() {
    return this.transitionTo(CoffeeMachine.STATES.IDLE)
  }
}

// 测试
async function test() {
  const machine = new CoffeeMachine()

  // 监听状态变化
  machine.on('stateChange', ({ from, to }) => {
    console.log(`状态: ${from} -> ${to}`)
  })

  machine.on('coffeeReady', () => {
    console.log('☕ 咖啡好了!')
  })

  await machine.addWater()
  await machine.addBeans()
  await machine.brew()
  await machine.reset()
}

test()

关键点

  • 状态机模式:用配置对象定义状态和转换规则,而非硬编码 if-else
  • 单向流转:状态只能按预定路径转换,防止非法操作
  • 事件驱动:通过发布订阅模式解耦状态变化和业务逻辑
  • 异步支持:真实场景中状态转换往往是异步的,需要用 Promise 处理
  • 可扩展性:新增状态只需修改配置,不改动核心逻辑