您现在的位置是:网站首页 > 状态模式(State)的对象行为变化管理文章详情

状态模式(State)的对象行为变化管理

状态模式是一种行为设计模式,允许对象在其内部状态改变时改变其行为。这种模式将状态封装成独立的类,并将请求委托给当前状态对象,从而消除庞大的条件分支语句,使代码更易维护和扩展。在JavaScript中,状态模式常用于管理复杂的状态逻辑,比如用户界面交互、游戏角色行为或工作流引擎。

状态模式的核心思想

状态模式的核心在于将对象的行为委托给表示当前状态的对象。一个经典的例子是电灯的开关控制。传统实现可能使用条件语句判断当前状态,而状态模式则将"开"和"关"两种状态抽象为独立的状态类。

// 传统实现方式
class Light {
  constructor() {
    this.state = 'off'
  }
  
  press() {
    if (this.state === 'off') {
      console.log('开灯')
      this.state = 'on'
    } else {
      console.log('关灯')
      this.state = 'off'
    }
  }
}

这种实现方式在状态较少时可行,但随着状态增加,代码会变得难以维护。状态模式通过将每个状态封装为独立类来解决这个问题。

状态模式的基本结构

状态模式通常包含三个主要部分:

  1. Context(上下文):定义客户端感兴趣的接口,维护一个具体状态子类的实例
  2. State(状态接口):定义一个接口以封装与Context的特定状态相关的行为
  3. ConcreteState(具体状态):实现状态接口,每个子类实现一个与Context状态相关的行为
// 状态接口
class State {
  handle(context) {}
}

// 具体状态:关闭状态
class OffState extends State {
  handle(context) {
    console.log('开灯')
    context.setState(new OnState())
  }
}

// 具体状态:开启状态
class OnState extends State {
  handle(context) {
    console.log('关灯')
    context.setState(new OffState())
  }
}

// 上下文
class Light {
  constructor() {
    this.setState(new OffState())
  }
  
  setState(state) {
    this.state = state
  }
  
  press() {
    this.state.handle(this)
  }
}

// 使用
const light = new Light()
light.press() // 开灯
light.press() // 关灯

JavaScript中的状态模式实现

在JavaScript中,由于语言的动态特性,我们可以采用更灵活的方式实现状态模式。下面是一个订单状态管理的例子:

// 订单状态基类
class OrderState {
  constructor(order) {
    this.order = order
  }
  
  cancel() {
    throw new Error('当前状态不允许此操作')
  }
  
  pay() {
    throw new Error('当前状态不允许此操作')
  }
  
  ship() {
    throw new Error('当前状态不允许此操作')
  }
  
  deliver() {
    throw new Error('当前状态不允许此操作')
  }
}

// 待支付状态
class PendingPaymentState extends OrderState {
  pay() {
    console.log('处理支付...')
    this.order.setState(new ProcessingState(this.order))
  }
  
  cancel() {
    console.log('取消订单')
    this.order.setState(new CancelledState(this.order))
  }
}

// 处理中状态
class ProcessingState extends OrderState {
  ship() {
    console.log('发货处理中...')
    this.order.setState(new ShippedState(this.order))
  }
}

// 已发货状态
class ShippedState extends OrderState {
  deliver() {
    console.log('订单已送达')
    this.order.setState(new DeliveredState(this.order))
  }
}

// 订单类
class Order {
  constructor() {
    this.setState(new PendingPaymentState(this))
  }
  
  setState(state) {
    this.state = state
    console.log(`订单状态变更为: ${state.constructor.name}`)
  }
  
  // 委托给当前状态
  cancel() { this.state.cancel() }
  pay() { this.state.pay() }
  ship() { this.state.ship() }
  deliver() { this.state.deliver() }
}

// 使用示例
const order = new Order()
order.pay()    // 处理支付... 状态变更为ProcessingState
order.ship()   // 发货处理中... 状态变更为ShippedState
order.deliver() // 订单已送达 状态变更为DeliveredState

状态模式的优缺点

优点

  1. 单一职责原则:将与特定状态相关的代码放在独立的类中
  2. 开闭原则:无需修改已有状态类和上下文就能引入新状态
  3. 消除庞大的条件语句:状态转换逻辑分布在状态类之间,减少了上下文中的条件判断
  4. 状态转换显式化:状态转换更加明确,通常作为状态类方法调用的结果

缺点

  1. 可能过度设计:如果状态很少且很少变化,使用状态模式可能会增加不必要的复杂性
  2. 状态类数量膨胀:对于复杂的状态机,可能导致大量的小类
  3. 上下文与状态耦合:状态类通常需要反向引用上下文对象,可能导致双向依赖

状态模式与策略模式的比较

状态模式和策略模式在结构上非常相似,但它们的意图不同:

  • 策略模式:客户端主动选择算法策略,策略之间通常相互独立
  • 状态模式:状态转换通常由状态类内部决定,客户端不直接选择状态
// 策略模式示例
class CompressionStrategy {
  compress(files) {}
}

class ZipStrategy extends CompressionStrategy {
  compress(files) {
    console.log('使用ZIP压缩')
  }
}

class RarStrategy extends CompressionStrategy {
  compress(files) {
    console.log('使用RAR压缩')
  }
}

class Compressor {
  constructor(strategy) {
    this.strategy = strategy
  }
  
  setStrategy(strategy) {
    this.strategy = strategy
  }
  
  compress(files) {
    this.strategy.compress(files)
  }
}

// 客户端主动选择策略
const compressor = new Compressor(new ZipStrategy())
compressor.compress(['file1.txt', 'file2.txt'])
compressor.setStrategy(new RarStrategy())
compressor.compress(['file1.txt', 'file2.txt'])

状态模式在前端的应用场景

1. UI组件状态管理

复杂的UI组件通常有多种状态(如加载中、加载成功、加载失败、禁用等),状态模式可以优雅地管理这些状态。

// 加载器组件状态管理
class LoaderState {
  constructor(loader) {
    this.loader = loader
  }
  
  render() {
    throw new Error('必须实现render方法')
  }
}

class LoadingState extends LoaderState {
  render() {
    return '<div class="loader">加载中...</div>'
  }
}

class SuccessState extends LoaderState {
  render() {
    return `<div class="content">${this.loader.data}</div>`
  }
}

class ErrorState extends LoaderState {
  render() {
    return '<div class="error">加载失败</div>'
  }
}

class Loader {
  constructor() {
    this.setState(new LoadingState(this))
  }
  
  setState(state) {
    this.state = state
    this.updateUI()
  }
  
  updateUI() {
    document.getElementById('app').innerHTML = this.state.render()
  }
  
  async loadData() {
    try {
      this.setState(new LoadingState(this))
      const response = await fetch('https://api.example.com/data')
      this.data = await response.json()
      this.setState(new SuccessState(this))
    } catch (error) {
      this.setState(new ErrorState(this))
    }
  }
}

2. 游戏开发中的角色状态

游戏角色通常有多种状态(站立、行走、跳跃、攻击等),状态模式非常适合这种场景。

// 游戏角色状态管理
class CharacterState {
  constructor(character) {
    this.character = character
  }
  
  handleInput(input) {}
  update() {}
}

class StandingState extends CharacterState {
  handleInput(input) {
    if (input === 'PRESS_UP') {
      this.character.setState(new JumpingState(this.character))
    } else if (input === 'PRESS_LEFT' || input === 'PRESS_RIGHT') {
      this.character.setState(new WalkingState(this.character))
    }
  }
  
  update() {
    this.character.velocityY = 0
  }
}

class WalkingState extends CharacterState {
  handleInput(input) {
    if (input === 'RELEASE_LEFT' || input === 'RELEASE_RIGHT') {
      this.character.setState(new StandingState(this.character))
    } else if (input === 'PRESS_UP') {
      this.character.setState(new JumpingState(this.character))
    }
  }
  
  update() {
    if (this.character.input === 'PRESS_LEFT') {
      this.character.velocityX = -5
    } else if (this.character.input === 'PRESS_RIGHT') {
      this.character.velocityX = 5
    }
  }
}

class JumpingState extends CharacterState {
  constructor(character) {
    super(character)
    this.jumpHeight = 15
  }
  
  handleInput(input) {
    // 跳跃过程中不能改变状态
  }
  
  update() {
    this.character.velocityY -= 0.5 // 重力
    this.jumpHeight--
    
    if (this.jumpHeight <= 0) {
      this.character.setState(new StandingState(this.character))
    }
  }
}

class Character {
  constructor() {
    this.velocityX = 0
    this.velocityY = 0
    this.input = null
    this.setState(new StandingState(this))
  }
  
  setState(state) {
    this.state = state
  }
  
  handleInput(input) {
    this.input = input
    this.state.handleInput(input)
  }
  
  update() {
    this.state.update()
    // 更新角色位置
    this.x += this.velocityX
    this.y += this.velocityY
  }
}

状态模式与有限状态机(FSM)

有限状态机是状态模式的理论基础,它由以下部分组成:

  1. 一组有限的状态
  2. 一个初始状态
  3. 一组输入事件
  4. 状态转换函数
  5. 一组可能产生的输出

在JavaScript中,我们可以使用状态模式实现FSM。下面是一个交通灯状态机的实现:

// 交通灯状态机
class TrafficLightState {
  constructor(light) {
    this.light = light
  }
  
  change() {}
}

class RedState extends TrafficLightState {
  change() {
    console.log('红灯 -> 绿灯')
    this.light.setState(new GreenState(this.light))
  }
}

class GreenState extends TrafficLightState {
  change() {
    console.log('绿灯 -> 黄灯')
    this.light.setState(new YellowState(this.light))
  }
}

class YellowState extends TrafficLightState {
  change() {
    console.log('黄灯 -> 红灯')
    this.light.setState(new RedState(this.light))
  }
}

class TrafficLight {
  constructor() {
    this.setState(new RedState(this))
  }
  
  setState(state) {
    this.state = state
    this.showCurrentState()
  }
  
  change() {
    this.state.change()
  }
  
  showCurrentState() {
    console.log(`当前信号灯: ${this.state.constructor.name.replace('State', '')}`)
  }
}

// 模拟交通灯变化
const trafficLight = new TrafficLight()
trafficLight.change() // 红灯 -> 绿灯
trafficLight.change() // 绿灯 -> 黄灯
trafficLight.change() // 黄灯 -> 红灯

状态模式与Redux状态管理

Redux等状态管理库虽然也管理状态,但与状态模式有本质区别:

  1. Redux:集中式状态管理,通过纯函数(reducer)处理状态变更
  2. 状态模式:分布式状态管理,行为分散在各个状态类中

不过,我们可以结合两者优势。下面是一个Redux中间件示例,它使用状态模式管理异步操作:

// 异步操作状态
const asyncStates = {
  idle: {
    start: () => ({ status: 'pending' })
  },
  pending: {
    resolve: (data) => ({ status: 'success', data }),
    reject: (error) => ({ status: 'error', error })
  },
  success: {
    reset: () => ({ status: 'idle' })
  },
  error: {
    retry: () => ({ status: 'pending' }),
    reset: () => ({ status: 'idle' })
  }
}

// 状态感知的Redux中间件
const statePatternMiddleware = store => next => action => {
  const state = store.getState()
  const currentStatus = state.asyncOperation.status
  const stateBehavior = asyncStates[currentStatus]
  
  if (stateBehavior && stateBehavior[action.type]) {
    const newState = stateBehavior[action.type](action.payload)
    return next({ type: 'ASYNC_STATE_CHANGE', payload: newState })
  }
  
  return next(action)
}

// Reducer
function asyncReducer(state = { status: 'idle' }, action) {
  if (action.type === 'ASYNC_STATE_CHANGE') {
    return { ...state, ...action.payload }
  }
  return state
}

状态模式的变体与扩展

1. 状态表驱动

对于简单的状态机,可以使用表驱动方式替代状态类:

// 表驱动状态机
const stateMachine = {
  off: {
    press: (light) => {
      console.log('开灯')
      light.setState('on')
    }
  },
  on: {
    press: (light) => {
      console.log('关灯')
      light.setState('off')
    }
  }
}

class Light {
  constructor() {
    this.setState('off')
  }
  
  setState(state) {
    this.state = state
  }
  
  press() {
    stateMachine[this.state].press(this)
  }
}

2. 分层状态模式

当状态有共性行为时,可以使用分层状态模式(Hierarchical State Pattern):

// 基础状态
class ConnectionState {
  constructor(connection) {
    this.connection = connection
  }
  
  open() {
    throw new Error('当前状态不允许此操作')
  }
  
  close() {
    throw new Error('当前状态不允许此操作')
  }
  
  transmit(data) {
    throw new Error('当前状态不允许此操作')
  }
}

// 连接已建立状态
class EstablishedState extends ConnectionState {
  transmit(data) {
    console.log(`传输数据: ${data}`)
  }
  
  close() {
    console.log('关闭连接')
    this.connection.setState(new ClosedState(this.connection))
  }
}

// TCP连接状态
class TcpEstablishedState extends EstablishedState {
  transmit(data) {
    console.log(`TCP传输: ${data}`)
    super.transmit(data)
  }
  
  close() {
    console.log('发送FIN包')
    this.connection.setState(new TcpClosingState(this.connection))
  }
}

// TCP关闭中状态
class TcpClosingState extends ConnectionState {
  open() {
    console.log('连接正在关闭,无法重新打开')
  }
  
  close() {
    console.log('等待ACK...')
    this.connection.setState(new ClosedState(this.connection))
  }
}

状态模式的性能考量

虽然状态模式提供了优秀的可维护性,但在性能敏感的场景需要考虑:

  1. 状态对象创建开销:频繁创建状态对象可能带来性能问题
  2. 内存占用:每个状态对象都会占用内存
  3. 方法调用开销:委托调用比直接调用稍慢

优化策略包括:

  • 使用单例状态对象(如果状态无实例数据)
  • 采用享元模式共享状态对象
  • 对于简单状态机,使用表驱动方式
// 单例状态对象示例
class OffState extends State {
  handle(context) {
    console.log('开灯')
    context.setState(OnState.instance)
  }
}
OffState.instance = new OffState()

class OnState extends State {
  handle(context) {
    console.log('关灯')
    context.setState(OffState.instance)
  }
}
OnState.instance = new OnState()

状态模式与TypeScript

使用TypeScript可以增强状态模式的安全性,通过接口和类型检查确保状态转换的正确性:

interface OrderState {
  cancel(): void
  pay(): void
  ship(): void
  deliver(): void
}

class Order {
  private state: OrderState
  
  constructor() {
    this.setState(new PendingPaymentState(this))
  }
  
  setState(state: OrderState) {
    this.state = state
  }
  
  cancel() { this.state.cancel() }
  pay() { this.state.pay() }
  ship() { this.state.ship() }
  deliver() { this.state.deliver() }
}

class PendingPaymentState implements OrderState {
  constructor(private order: Order) {}
  
  pay() {
    console.log('处理支付...')
    this.order.setState(new ProcessingState(this.order))
  }
  
  cancel() {
    console.log('取消订单')
    this.order.setState(new CancelledState(this.order))
  }
  
  ship() { throw new Error('待支付状态不能发货') }
  deliver() { throw new Error('待支付状态不能交付') }
}

状态模式在复杂UI流程中的应用

对于多步骤表单或复杂UI流程,状态模式能很好地管理各个步骤的状态和转换:

// 多步骤表单状态管理
class FormState {
  constructor(wizard) {
    this.wizard = wizard
  }
  
  next() {}
  prev() {}
  submit() {}
}

class Step1State extends FormState {
  next() {
    console.log('从步骤1转到步骤2')
    this.wizard.setState(new Step2State(this.wizard))
  }
  
  prev() {
    console.log('已经在第一步,无法后退')
  }
  
  submit() {
    console.log('请完成所有步骤后再提交')
  }
}

class Step2State extends FormState {
  next() {
    console.log('从步骤2转到步骤3')
    this.wizard.setState(new Step3State(this.wizard))
  }
  
  prev() {
    console.log('从步骤2返回步骤1')
    this.wizard.setState(new Step1State(this.wizard))
  }
  
  submit() {
    console.log('请完成所有步骤后再提交')
  }
}

class Step3State extends FormState {
  next() {
    console.log('已经是最后一步')
  }
  
  prev() {
    console.log('从步骤3返回步骤2')

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步