咖啡机状态切换机制

用状态模式改造咖啡机的状态管理

问题

咖啡机有多个状态:空闲、加水、加热、冲泡、完成。传统实现用大量 if-else 判断当前状态,代码难以维护。请用状态模式改造状态切换机制。

解答

原始实现(问题代码)

class CoffeeMachine {
  constructor() {
    this.state = 'idle'
  }

  action(command) {
    // 大量 if-else,难以维护
    if (this.state === 'idle') {
      if (command === 'addWater') {
        this.state = 'filling'
        console.log('开始加水')
      }
    } else if (this.state === 'filling') {
      if (command === 'heat') {
        this.state = 'heating'
        console.log('开始加热')
      }
    } else if (this.state === 'heating') {
      if (command === 'brew') {
        this.state = 'brewing'
        console.log('开始冲泡')
      }
    }
    // ... 更多状态判断
  }
}

状态模式改造

// 状态基类
class State {
  constructor(machine) {
    this.machine = machine
  }

  addWater() {
    console.log('当前状态无法执行此操作')
  }

  heat() {
    console.log('当前状态无法执行此操作')
  }

  brew() {
    console.log('当前状态无法执行此操作')
  }

  take() {
    console.log('当前状态无法执行此操作')
  }
}

// 空闲状态
class IdleState extends State {
  addWater() {
    console.log('开始加水...')
    this.machine.setState(this.machine.fillingState)
  }
}

// 加水状态
class FillingState extends State {
  heat() {
    console.log('水已加满,开始加热...')
    this.machine.setState(this.machine.heatingState)
  }
}

// 加热状态
class HeatingState extends State {
  brew() {
    console.log('水已加热,开始冲泡...')
    this.machine.setState(this.machine.brewingState)
  }
}

// 冲泡状态
class BrewingState extends State {
  constructor(machine) {
    super(machine)
  }

  // 冲泡完成自动转换到完成状态
  complete() {
    console.log('冲泡完成!')
    this.machine.setState(this.machine.readyState)
  }
}

// 完成状态
class ReadyState extends State {
  take() {
    console.log('取走咖啡,机器恢复空闲')
    this.machine.setState(this.machine.idleState)
  }
}

// 咖啡机
class CoffeeMachine {
  constructor() {
    // 初始化所有状态
    this.idleState = new IdleState(this)
    this.fillingState = new FillingState(this)
    this.heatingState = new HeatingState(this)
    this.brewingState = new BrewingState(this)
    this.readyState = new ReadyState(this)

    // 初始状态为空闲
    this.currentState = this.idleState
  }

  setState(state) {
    this.currentState = state
  }

  getStateName() {
    const stateMap = {
      [this.idleState]: 'idle',
      [this.fillingState]: 'filling',
      [this.heatingState]: 'heating',
      [this.brewingState]: 'brewing',
      [this.readyState]: 'ready'
    }
    return stateMap[this.currentState]
  }

  // 委托给当前状态处理
  addWater() {
    this.currentState.addWater()
  }

  heat() {
    this.currentState.heat()
  }

  brew() {
    this.currentState.brew()
  }

  take() {
    this.currentState.take()
  }
}

// 使用示例
const machine = new CoffeeMachine()

machine.addWater() // 开始加水...
machine.heat() // 水已加满,开始加热...
machine.brew() // 水已加热,开始冲泡...
machine.brewingState.complete() // 冲泡完成!
machine.take() // 取走咖啡,机器恢复空闲

// 测试非法操作
machine.brew() // 当前状态无法执行此操作

简化版:用对象映射实现

const createCoffeeMachine = () => {
  // 状态转换表
  const transitions = {
    idle: { addWater: 'filling' },
    filling: { heat: 'heating' },
    heating: { brew: 'brewing' },
    brewing: { complete: 'ready' },
    ready: { take: 'idle' }
  }

  // 状态动作
  const actions = {
    addWater: () => console.log('开始加水...'),
    heat: () => console.log('开始加热...'),
    brew: () => console.log('开始冲泡...'),
    complete: () => console.log('冲泡完成!'),
    take: () => console.log('取走咖啡')
  }

  let state = 'idle'

  return {
    getState: () => state,

    dispatch(action) {
      const nextState = transitions[state]?.[action]

      if (nextState) {
        actions[action]?.()
        state = nextState
        return true
      }

      console.log(`状态 ${state} 下无法执行 ${action}`)
      return false
    }
  }
}

// 使用
const machine = createCoffeeMachine()
machine.dispatch('addWater') // 开始加水...
machine.dispatch('heat') // 开始加热...
machine.dispatch('brew') // 开始冲泡...
machine.dispatch('complete') // 冲泡完成!
machine.dispatch('take') // 取走咖啡
console.log(machine.getState()) // idle

关键点

  • 状态模式:将每个状态封装成独立的类,状态自己决定如何响应操作
  • 消除 if-else:通过多态替代条件判断,新增状态只需添加新类
  • 状态转换表:用对象映射定义合法的状态转换,简洁且易于维护
  • 单一职责:每个状态类只关心自己的行为,咖啡机类只负责委托
  • 开闭原则:扩展新状态不需要修改现有代码