设计模式之状态模式

设计模式之状态模式

意图

状态模式(State) 是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。

适用场景

  • 如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。
  • 如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。
  • 当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。

结构

结构说明

img

  1. 上下文 (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。
  2. 状态 (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。
  3. 具体状态 (Concrete States) 会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。
    • 状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。
  4. 上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。

结构代码范式

State : 定义一个接口以封装与 Context 的一个特定状态相关的行为。

1
2
3
abstract class State {
public abstract void Handle(Context context);
}

ConcreteState : 每一个子类实现一个与 Context 的一个状态相关的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
class ConcreteStateA extends State {
@Override
public void Handle(Context context) {
context.SetState(new ConcreteStateB());
}
}

class ConcreteStateB extends State {
@Override
public void Handle(Context context) {
context.SetState(new ConcreteStateA());
}
}

Context : 维护一个 ConcreteState 子类的实例,这个实例定义当前的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Context {
private State state;
public Context(State state) {
this.state = state;
}

public void SetState(State state) {
this.state = state;
System.out.println("当前状态:" + state.getClass().getName());
}
public State GetState() {
return state;
}

public void Request() {
state.Handle(this);
}
}

客户端

1
2
3
4
5
6
7
public class StatePattern {
public static void main(String[] args) {
Context c = new Context(new ConcreteStateA());
c.Request();
c.Request();
}
}

输出

1
2
当前状态:ConcreteStateB
当前状态:ConcreteStateA

伪代码

在本例中, 状态模式将根据当前回放状态, 让媒体播放器中的相同控件完成不同的行为。

img

播放器的主要对象总是会连接到一个负责播放器绝大部分工作的状态对象中。 部分操作会更换播放器当前的状态对象, 以此改变播放器对于用户互动所作出的反应。

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// 音频播放器(Audio­Player)类即为上下文。它还会维护指向状态类实例的引用,
// 该状态类则用于表示音频播放器当前的状态。
class AudioPlayer is
field state: State
field UI, volume, playlist, currentSong

constructor AudioPlayer() is
this.state = new ReadyState(this)

// 上下文会将处理用户输入的工作委派给状态对象。由于每个状态都以不
// 同的方式处理输入,其结果自然将依赖于当前所处的状态。
UI = new UserInterface()
UI.lockButton.onClick(this.clickLock)
UI.playButton.onClick(this.clickPlay)
UI.nextButton.onClick(this.clickNext)
UI.prevButton.onClick(this.clickPrevious)

// 其他对象必须能切换音频播放器当前所处的状态。
method changeState(state: State) is
this.state = state

// UI 方法会将执行工作委派给当前状态。
method clickLock() is
state.clickLock()
method clickPlay() is
state.clickPlay()
method clickNext() is
state.clickNext()
method clickPrevious() is
state.clickPrevious()

// 状态可调用上下文的一些服务方法。
method startPlayback() is
// ...
method stopPlayback() is
// ...
method nextSong() is
// ...
method previousSong() is
// ...
method fastForward(time) is
// ...
method rewind(time) is
// ...


// 所有具体状态类都必须实现状态基类声明的方法,并提供反向引用指向与状态相
// 关的上下文对象。状态可使用反向引用将上下文转换为另一个状态。
abstract class State is
protected field player: AudioPlayer

// 上下文将自身传递给状态构造函数。这可帮助状态在需要时获取一些有用的
// 上下文数据。
constructor State(player) is
this.player = player

abstract method clickLock()
abstract method clickPlay()
abstract method clickNext()
abstract method clickPrevious()


// 具体状态会实现与上下文状态相关的多种行为。
class LockedState extends State is

// 当你解锁一个锁定的播放器时,它可能处于两种状态之一。
method clickLock() is
if (player.playing)
player.changeState(new PlayingState(player))
else
player.changeState(new ReadyState(player))

method clickPlay() is
// 已锁定,什么也不做。

method clickNext() is
// 已锁定,什么也不做。

method clickPrevious() is
// 已锁定,什么也不做。


// 它们还可在上下文中触发状态转换。
class ReadyState extends State is
method clickLock() is
player.changeState(new LockedState(player))

method clickPlay() is
player.startPlayback()
player.changeState(new PlayingState(player))

method clickNext() is
player.nextSong()

method clickPrevious() is
player.previousSong()


class PlayingState extends State is
method clickLock() is
player.changeState(new LockedState(player))

method clickPlay() is
player.stopPlayback()
player.changeState(new ReadyState(player))

method clickNext() is
if (event.doubleclick)
player.nextSong()
else
player.fastForward(5)

method clickPrevious() is
if (event.doubleclick)
player.previous()
else
player.rewind(5)

案例

使用示例: 在 Java 语言中, 状态模式通常被用于将基于 switch语句的大型状态机转换为对象。

这里是核心 Java 程序库中一些状态模式的示例:

识别方法: 状态模式可通过受外部控制且能根据对象状态改变行为的方法来识别。

与其他模式的关系

  • 桥接模式状态模式策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。
  • 状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。

参考资料