状态模式
让对象在状态改变时自动切换行为
问题
什么是状态模式?如何在 JavaScript 中实现状态模式?
解答
状态模式允许对象在内部状态改变时改变其行为,看起来像是改变了对象的类。它将状态相关的行为封装到独立的状态类中,避免大量的条件判断语句。
基本实现
// 状态接口(抽象状态)
class State {
handle(context) {
throw new Error('子类必须实现 handle 方法')
}
}
// 具体状态:待机状态
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 Switch {
constructor() {
// 初始状态为关机
this.state = new OffState()
}
setState(state) {
this.state = state
}
// 按下开关,委托给当前状态处理
press() {
this.state.handle(this)
}
}
// 使用
const switcher = new Switch()
switcher.press() // 当前是关机状态,正在开机...
switcher.press() // 当前是开机状态,正在关机...
switcher.press() // 当前是关机状态,正在开机...
实际场景:订单状态流转
// 订单状态基类
class OrderState {
constructor(order) {
this.order = order
}
pay() {
console.log('当前状态不允许支付')
}
ship() {
console.log('当前状态不允许发货')
}
receive() {
console.log('当前状态不允许确认收货')
}
cancel() {
console.log('当前状态不允许取消')
}
}
// 待支付状态
class PendingPayment extends OrderState {
pay() {
console.log('支付成功')
this.order.setState(new PendingShipment(this.order))
}
cancel() {
console.log('订单已取消')
this.order.setState(new Cancelled(this.order))
}
}
// 待发货状态
class PendingShipment extends OrderState {
ship() {
console.log('已发货')
this.order.setState(new Shipped(this.order))
}
}
// 已发货状态
class Shipped extends OrderState {
receive() {
console.log('确认收货,订单完成')
this.order.setState(new Completed(this.order))
}
}
// 已完成状态
class Completed extends OrderState {
// 所有操作都不允许,使用默认行为
}
// 已取消状态
class Cancelled extends OrderState {
// 所有操作都不允许,使用默认行为
}
// 订单上下文
class Order {
constructor() {
this.state = new PendingPayment(this)
}
setState(state) {
this.state = state
console.log(`订单状态变更为: ${state.constructor.name}`)
}
pay() {
this.state.pay()
}
ship() {
this.state.ship()
}
receive() {
this.state.receive()
}
cancel() {
this.state.cancel()
}
}
// 使用
const order = new Order()
order.ship() // 当前状态不允许发货
order.pay() // 支付成功 -> 订单状态变更为: PendingShipment
order.cancel() // 当前状态不允许取消
order.ship() // 已发货 -> 订单状态变更为: Shipped
order.receive() // 确认收货,订单完成 -> 订单状态变更为: Completed
简化版:使用对象字面量
// 用对象代替类,更轻量
const trafficLight = {
// 状态定义
states: {
green: {
color: '绿灯',
next: 'yellow',
duration: 30
},
yellow: {
color: '黄灯',
next: 'red',
duration: 5
},
red: {
color: '红灯',
next: 'green',
duration: 25
}
},
// 当前状态
current: 'green',
// 获取当前状态信息
getInfo() {
const state = this.states[this.current]
return `${state.color},持续 ${state.duration} 秒`
},
// 切换到下一个状态
change() {
const state = this.states[this.current]
this.current = state.next
console.log(`切换到: ${this.getInfo()}`)
}
}
// 使用
console.log(trafficLight.getInfo()) // 绿灯,持续 30 秒
trafficLight.change() // 切换到: 黄灯,持续 5 秒
trafficLight.change() // 切换到: 红灯,持续 25 秒
trafficLight.change() // 切换到: 绿灯,持续 30 秒
关键点
- 消除条件分支:用多态代替 if-else 或 switch-case,每个状态类只关心自己的行为
- 状态转换封装:状态切换逻辑在状态类内部完成,上下文不需要知道转换规则
- 开闭原则:新增状态只需添加新的状态类,不修改现有代码
- 适用场景:对象行为随状态改变而改变,且状态数量较多时(如订单流程、游戏角色状态、UI 组件状态)
- 与策略模式区别:状态模式的状态之间知道彼此存在并主动切换,策略模式的策略相互独立由外部选择
目录