发布于 

设计模式之状态模式

状态模式:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为

状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。

状态模式结构:

  • 环境(Context):称为上下文类或环境类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
  • 抽象状态(State):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
  • 具体状态(Concrete State):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。

例子

我们来想象这样一个场景:有一个电灯,电灯上面只有一个开关。当电灯开着的时候,此时
按下开关,电灯会切换到关闭状态;再按一次开关,电灯又将被打开。同一个开关按钮,在不同
的状态下,表现出来的行为是不一样的。

现在用代码(不使用状态模式)来描述这个场景:

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
interface ILight{
state:string,
setState():void,
buttonWasPressed():void
}

class Light implements ILight{
//开关按钮
button:HTMLElement|null = null
state="off";
setState(): void {
if(this.state === 'off'){
this.state = 'on'
console.log('开灯')
}else if(this.state==='on'){
this.state = 'off'
console.log("关灯")
}
}
buttonWasPressed(): void {
this.setState()
}
init(){
let button = document.createElement('button');
button.innerHTML = "开关";
this.button = document.body.appendChild(button);
//点击关闭或打开
this.button.addEventListener('click',()=>{
this.buttonWasPressed()
})
}
}

let lightContext = new Light()
lightContext.init()

这只是普通的电灯开关,要么关要么开。许多酒店里有另外一种电灯,这种电灯也只有一个开关,但它的表现是:第一次按下打开弱光,第二次按下打开强光,第三次才是关闭电灯。现在必须改造上面的代码来完成这种新型电灯的制造:

1
2
3
4
5
6
7
8
9
10
11
12
setState(): void {
if(this.state === 'off'){
this.state = 'weakLight'
console.log('弱光')
}else if(this.state==='weakLight'){
this.state = 'strongLight'
console.log("强光")
}else if((this.state==='strongLight'){
this.state = 'off'
console.log("关灯")
}
}

现在这个反例先告一段落,我们来考虑一下上述程序的缺点。

  1. 所有跟状态有关的行为,都被封装在 setState方法里,如果以后这个电灯又增加了强强光、超强光和终极强光,那我们将无法预计这个方法将膨胀到什么地步。
  2. 状态之间的切换关系,不过是往 setState方法里堆砌 if、else 语句,增加或者修改一个状态可能需要改变若干个操作,这使 buttonWasPressed 更加难以阅读和维护。

状态模式改进电灯程序

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
64
65
66
67
68
69
70
71
72
73
74
75
76
//抽象状态类
abstract class LightState{
//定义一个环境角色,也就是封装状态的变化引起功能的变化
protected context:Context|null = null;

public setContext(context:Context){
this.context = context
}

//点击按钮切换状态,由子类实现
public abstract buttonWasPressed():void
}

//弱光状态
class OffLightState extends LightState{
public buttonWasPressed(): void {
console.log("弱光");
this.context?.setLightState(Context.weakLightState)

}
}
//强光状态
class WeakLightState extends LightState{
public buttonWasPressed(): void {
console.log("强光");
this.context?.setLightState(Context.strongLightState)
}
}
//关灯状态
class StrongLightState extends LightState{
public buttonWasPressed(): void {
console.log("关灯");
this.context?.setLightState(Context.offLightState)
}
}


//上下文
class Context{
//定义出灯的所有状态
static readonly offLightState = new OffLightState();
static readonly weakLightState = new WeakLightState();
static readonly strongLightState = new StrongLightState();

//定义开关
button:HTMLElement|null = null;

//定义一个电灯当前所处的状态
currLightState:LightState|null=null
//切换电灯状态
setLightState( lightState:LightState){
//设置电灯当前状态
this.currLightState = lightState;
//把当前上下文环境通知到各个实现类中
this.currLightState.setContext(this)
}

init(){
let button = document.createElement('button');
button.innerHTML = "开关";
this.button = document.body.appendChild(button);
//为电灯设置一个初始状态
this.setLightState(new OffLightState())

this.button.addEventListener('click',()=>{
this.currLightState?.buttonWasPressed()
})
}
}

let lightContext = new Context()
lightContext.init()

// 弱光
// 强光
// 关灯

是使用状态模式的好处很明显,它可以使每一种状态和它对应的行为之间的关系局部化,这些行为被分散和封装在各自对应的状态类之中,便于阅读和管理代码。

另外,状态之间的切换都被分布在状态类内部,这使得我们无需编写过多的 if、else 条件分支语言来控制状态之间的转换。

当我们需要为 light 对象增加一种新的状态时,只需要增加一个新的状态类,再稍稍改变一些现有的代码即可。