您现在的位置是:网站首页 > 状态模式(State)的对象行为变化管理文章详情
状态模式(State)的对象行为变化管理
陈川
【
JavaScript
】
33097人已围观
13031字
状态模式是一种行为设计模式,允许对象在其内部状态改变时改变其行为。这种模式将状态封装成独立的类,并将请求委托给当前状态对象,从而消除庞大的条件分支语句,使代码更易维护和扩展。在JavaScript中,状态模式常用于管理复杂的状态逻辑,比如用户界面交互、游戏角色行为或工作流引擎。
状态模式的核心思想
状态模式的核心在于将对象的行为委托给表示当前状态的对象。一个经典的例子是电灯的开关控制。传统实现可能使用条件语句判断当前状态,而状态模式则将"开"和"关"两种状态抽象为独立的状态类。
// 传统实现方式
class Light {
constructor() {
this.state = 'off'
}
press() {
if (this.state === 'off') {
console.log('开灯')
this.state = 'on'
} else {
console.log('关灯')
this.state = 'off'
}
}
}
这种实现方式在状态较少时可行,但随着状态增加,代码会变得难以维护。状态模式通过将每个状态封装为独立类来解决这个问题。
状态模式的基本结构
状态模式通常包含三个主要部分:
- Context(上下文):定义客户端感兴趣的接口,维护一个具体状态子类的实例
- State(状态接口):定义一个接口以封装与Context的特定状态相关的行为
- 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
状态模式的优缺点
优点
- 单一职责原则:将与特定状态相关的代码放在独立的类中
- 开闭原则:无需修改已有状态类和上下文就能引入新状态
- 消除庞大的条件语句:状态转换逻辑分布在状态类之间,减少了上下文中的条件判断
- 状态转换显式化:状态转换更加明确,通常作为状态类方法调用的结果
缺点
- 可能过度设计:如果状态很少且很少变化,使用状态模式可能会增加不必要的复杂性
- 状态类数量膨胀:对于复杂的状态机,可能导致大量的小类
- 上下文与状态耦合:状态类通常需要反向引用上下文对象,可能导致双向依赖
状态模式与策略模式的比较
状态模式和策略模式在结构上非常相似,但它们的意图不同:
- 策略模式:客户端主动选择算法策略,策略之间通常相互独立
- 状态模式:状态转换通常由状态类内部决定,客户端不直接选择状态
// 策略模式示例
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)
有限状态机是状态模式的理论基础,它由以下部分组成:
- 一组有限的状态
- 一个初始状态
- 一组输入事件
- 状态转换函数
- 一组可能产生的输出
在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等状态管理库虽然也管理状态,但与状态模式有本质区别:
- Redux:集中式状态管理,通过纯函数(reducer)处理状态变更
- 状态模式:分布式状态管理,行为分散在各个状态类中
不过,我们可以结合两者优势。下面是一个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))
}
}
状态模式的性能考量
虽然状态模式提供了优秀的可维护性,但在性能敏感的场景需要考虑:
- 状态对象创建开销:频繁创建状态对象可能带来性能问题
- 内存占用:每个状态对象都会占用内存
- 方法调用开销:委托调用比直接调用稍慢
优化策略包括:
- 使用单例状态对象(如果状态无实例数据)
- 采用享元模式共享状态对象
- 对于简单状态机,使用表驱动方式
// 单例状态对象示例
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')