概念

状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态发生变化时改变其行为,使得对象看起来好像修改了其类


状态模式通常涉及三个核心角色:上下文(Context)、状态(State)和具体状态(Concrete State)

  • 上下文(Context)

    维护一个当前状态对象,并将状态相关的操作委托给当前状态对象处理

    上下文对象通常会包含一个状态对象的引用,并提供方法来切换当前状态和执行状态相关的操作

  • 状态(State)

    定义了一个接口用于封装与上下文相关的行为

    状态对象通常会包含多个方法,用于处理上下文对象的请求,并可能会改变上下文对象的状态

  • 具体状态(Concrete State)

    实现了状态接口,并负责实现具体的状态行为

    具体状态对象通常会包含状态相关的业务逻辑,并根据需要改变上下文对象的状态


状态模式的核心思想是将对象的状态和行为进行分离,使得状态的改变不会影响对象的行为,从而实现对象的灵活性和可扩展性

这种模式的优点在于,可以将状态的转换和状态的行为封装到不同的状态对象中,使得状态之间的转换更加灵活和可扩展

状态模式适用于以下情况:

  • 当对象的行为取决于其状态,并且状态可能在运行时发生变化时,可以使用状态模式
  • 当希望将状态的转换和状态的行为封装到不同的状态对象中,并且希望通过配置来动态地改变对象的行为时,状态模式也是一个很好的选择


举个简单的例子,考虑一个自动售货机系统

自动售货机的行为可能会根据不同的状态(如有货、缺货、正在出货等)而发生变化,而状态模式可以将每种状态抽象成一个状态对象,并根据当前状态来执行相应的行为

例如,当自动售货机处于有货状态时,可以执行出售商品的行为;当自动售货机处于缺货状态时,可以执行补货的行为

这样,可以通过改变状态对象来动态地改变自动售货机的行为,而不需要修改自动售货机的代码


实现条件

  1. 存在多个状态

    状态模式适用于存在多个状态,并且对象在不同状态下会有不同的行为的情况

  2. 状态之间存在转换关系

    状态模式适用于状态之间存在转换关系,并且状态转换是由一定的条件触发的情况

  3. 需要封装对象的状态

    状态模式适用于需要封装对象的状态,并且根据对象的状态来决定对象的行为的情况

  4. 行为随状态的改变而改变

    状态模式适用于对象的行为随状态的改变而改变的情况,状态模式将对象的行为分离成多个状态类,每个状态类负责管理对象在特定状态下的行为


优点

  1. 封装了状态相关行为

    状态模式将每个状态封装成一个类,使得每个状态都有独立的类实现,从而简化了状态之间的转换和管理

  2. 消除了大量的条件判断语句

    状态模式将对象的行为随状态的改变而改变,避免了大量的条件判断语句,使得代码更加清晰和易于维护

  3. 符合开闭原则

    状态模式符合开闭原则,可以通过增加新的状态类来扩展系统的功能,而无需修改已有的代码

  4. 增强了对象的封装性

    状态模式将对象的状态封装到不同的状态类中,使得对象的状态对外部是透明的,增强了对象的封装性

  5. 简化了对象的行为切换

    状态模式将对象的行为切换和状态转换都交由状态类来管理,使得对象的行为切换更加简单和灵活


缺点

  1. 可能会增加类的数量

    状态模式会将每个状态都封装成一个类,可能会导致系统中类的数量增加,从而增加系统的复杂度

  2. 状态之间的转换可能复杂

    状态模式要求状态之间存在转换关系,如果状态之间的转换关系比较复杂,可能会导致状态模式的实现变得复杂

  3. 可能导致逻辑分散

    状态模式将对象的行为切换和状态转换都交由状态类来管理,可能会导致对象的行为和状态分散到多个状态类中,增加了代码的理解和维护难度


实现方式

自动售货机类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* 自动售货机类,管理产品状态和行为。
*/
import VendingMachineState from './SubClass/VendingMachineState'
import HasProductState from './SubClass/HasProductState'
import SoldOutState from './SubClass/SoldOutState'

class VendingMachine {
/**
* 构造函数初始化自动售货机状态。
* @param {number} initialStock 初始库存量。
*/
constructor(initialStock) {
this.hasProductState = new HasProductState(this) // 当有产品时的状态
this.soldOutState = new SoldOutState(this) // 当产品售罄时的状态
this.currentState = initialStock > 0 ? this.hasProductState : this.soldOutState // 根据初始库存设置当前状态
}

/**
* 设置自动售货机的状态。
* @param {VendingMachineState} newState 新的状态对象。
*/
setState(newState) {
if (newState instanceof VendingMachineState) {
console.log(`状态变化:从${this.currentState.constructor.name}${newState.constructor.name}`)
this.currentState = newState
} else {
console.error("无效的状态对象")
}
}

/**
* 获取有产品状态对象。
* @returns {HasProductState} 有产品状态对象。
*/
getHasProductState() {
return this.hasProductState
}

/**
* 获取售罄状态对象。
* @returns {SoldOutState} 售罄状态对象。
*/
getSoldOutState() {
return this.soldOutState
}

/**
* 销售产品,行为取决于当前状态。
*/
sellProduct() {
this.currentState.sellProduct()
}

/**
* 补充库存,行为取决于当前状态。
*/
restock() {
this.currentState.restock()
}
}

export default VendingMachine


状态基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* VendingMachineState类定义了自动售货机状态的基类,为状态模式的一部分。
* 这个类主要是为了提供给子类继承,子类需要实现其中的sellProduct和restock方法。
*
* @param {Object} vendingMachine 对象引用,指向自动售货机实例,允许状态对象访问或修改自动售货机的状态。
*/
class VendingMachineState {
/**
* 构造函数,初始化VendingMachineState实例。
*
* @param {Object} vendingMachine 自动售货机实例,用于在状态中控制和访问售货机功能。
*/
constructor(vendingMachine) {
this.vendingMachine = vendingMachine; // 绑定自动售货机实例到当前状态对象
}

/**
* 抽象方法,要求子类实现售出商品的逻辑。
*
* @throws {Error} 如果子类未实现此方法,抛出错误提示。
*/
sellProduct() {
throw new Error('子类必须实现此方法') // 强制子类重写以实现具体售货逻辑
}

/**
* 抽象方法,要求子类实现补货的逻辑。
*
* @throws {Error} 如果子类未实现此方法,抛出错误提示。
*/
restock() {
throw new Error('子类必须实现此方法') // 强制子类重写以实现具体补货逻辑
}
}

// 导出VendingMachineState类作为模块的默认导出项,便于其他模块使用
export default VendingMachineState


状态子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 该类表示售货机已有商品的状态。
* 继承自VendingMachineState,以提供特定于售出商品状态的行为。
*/
import VendingMachineState from './VendingMachineState'

class HasProductState extends VendingMachineState {
/**
* 出售产品的方法。当售出产品时,将状态切换到售罄状态。
*/
sellProduct() {
console.log("商品已售出")
this.vendingMachine.setState(this.vendingMachine.getSoldOutState())
}

/**
* 尝试为售货机补货的方法。在已有商品的状态下,该操作不被允许,
* 所以只是打印一条信息并不进行任何操作。
*/
restock() {
console.log("无法补货,售货机中已有商品")
}
}

export default HasProductState



/**
* SoldOutState类继承自VendingMachineState,用于表示自动售货机售罄状态。
* 在这种状态下,自动售货机无法出售商品,但可以进行补货操作。
*/
import VendingMachineState from './VendingMachineState'

class SoldOutState extends VendingMachineState {
/**
* 尝试出售商品时的操作。
* 由于售罄状态,此操作将不执行任何动作,仅输出一条信息。
*/
sellProduct() {
console.log("无法售出,商品已售罄")
}

/**
* 执行补货操作。
* 当调用此方法时,自动售货机将改变状态为有货状态(HasProductState)。
*/
restock() {
console.log("补货成功")
// 切换状态至有货状态
this.vendingMachine.setState(this.vendingMachine.getHasProductState())
}
}

export default SoldOutState


怎么使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 导入VendingMachine类,该类实现了状态模式
*/
import VendingMachine from '../StatePattern'

/**
* 初始化一个拥有10个商品的自动售货机实例
*/
const vendingMachine = new VendingMachine(10)

/**
* 在有货状态下进行商品销售和补货操作的示例
*/
vendingMachine.sellProduct() // 输出:商品已售出
vendingMachine.restock() // 输出:无法补货,售货机中已有商品

/**
* 切换自动售货机到缺货状态,并进行操作示例
*/
vendingMachine.setState(vendingMachine.getSoldOutState())
vendingMachine.sellProduct() // 输出:无法售出,商品已售罄
vendingMachine.restock() // 输出:补货成功

/**
* 尝试传入无效状态进行测试
*/
vendingMachine.setState("InvalidState") // 输出:无效的状态对象


场景

  1. 自动售货机

    自动售货机是一个很好的状态模式的应用示例

    根据货物存量的不同,自动售货机的行为会发生变化,比如有货时可以售卖商品,缺货时需要进行补货等

  2. 订单状态管理

    在电子商务系统中,订单状态经常会发生变化,比如订单创建、支付、发货、完成等

    可以使用状态模式来管理订单的各种状态,使得订单对象能够根据不同状态执行不同的行为

  3. 文档编辑器

    在文档编辑器中,可以根据文档的当前状态(比如编辑中、已保存、已发布等)来决定编辑器的行为,比如保存、发布、撤销等操作

  4. 多媒体播放器

    在多媒体播放器中,播放器的行为会根据当前播放状态(比如播放、暂停、停止等)来改变,使用状态模式可以方便地管理播放器的状态转换

  5. 网络连接管理

    在网络应用中,网络连接的状态经常会发生变化,比如连接中、已连接、断开连接等

    状态模式可以用来管理网络连接的状态,以便根据不同的状态执行不同的操作


源代码