https://refactoringguru.cn/design-patterns/state

行为模式-状态模式

亦称: State

意图

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

问题

状态模式与有限状态机的概念紧密相关。

有限状态机。

其主要思想是程序在任意时刻仅可处于几种有限状态中。 在任何一个特定状态中, 程序的行为都不相同, 且可瞬间从一个状态切换到另一个状态。 不过, 根据当前状态, 程序可能会切换到另外一种状态, 也可能会保持当前状态不变。 这些数量有限且预先定义的状态切换规则被称为转移

你还可将该方法应用在对象上。 假如你有一个 文档Document类。 文档可能会处于 草稿Draft 、 审阅中Moderation和 已发布Published三种状态中的一种。 文档的 publish发布方法在不同状态下的行为略有不同:

  • 处于 草稿状态时, 它会将文档转移到审阅中状态。
  • 处于 审阅中状态时, 如果当前用户是管理员, 它会公开发布文档。
  • 处于 已发布状态时, 它不会进行任何操作。

文档对象的全部状态和转移。

状态机通常由众多条件运算符 ( ifswitch ) 实现, 可根据对象的当前状态选择相应的行为。 “状态” 通常只是对象中的一组成员变量值。 即使你之前从未听说过有限状态机, 你也很可能已经实现过状态模式。 下面的代码应该能帮助你回忆起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Document is
field state: string
// ...
method publish() is
switch (state)
"draft":
state = "moderation"
break
"moderation":
if (currentUser.role == 'admin')
state = "published"
break
"published":
// 什么也不做。
break
// ...

当我们逐步在 文档类中添加更多状态和依赖于状态的行为后, 基于条件语句的状态机就会暴露其最大的弱点。 为了能根据当前状态选择完成相应行为的方法, 绝大部分方法中会包含复杂的条件语句。 修改其转换逻辑可能会涉及到修改所有方法中的状态条件语句, 导致代码的维护工作非常艰难。

这个问题会随着项目进行变得越发严重。 我们很难在设计阶段预测到所有可能的状态和转换。 随着时间推移, 最初仅包含有限条件语句的简洁状态机可能会变成臃肿的一团乱麻。

解决方案

状态模式建议为对象的所有可能状态新建一个类, 然后将所有状态的对应行为抽取到这些类中。

原始对象被称为上下文 (context), 它并不会自行实现所有行为, 而是会保存一个指向表示当前状态的状态对象的引用, 且将所有与状态相关的工作委派给该对象。

文档将工作委派给一个状态对象。

如需将上下文转换为另外一种状态, 则需将当前活动的状态对象替换为另外一个代表新状态的对象。 采用这种方式是有前提的: 所有状态类都必须遵循同样的接口, 而且上下文必须仅通过接口与这些对象进行交互。

这个结构可能看上去与策略模式相似, 但有一个关键性的不同——在状态模式中, 特定状态知道其他所有状态的存在, 且能触发从一个状态到另一个状态的转换; 策略则几乎完全不知道其他策略的存在。

真实世界类比

智能手机的按键和开关会根据设备当前状态完成不同行为:

  • 当手机处于解锁状态时, 按下按键将执行各种功能。
  • 当手机处于锁定状态时, 按下任何按键都将解锁屏幕。
  • 当手机电量不足时, 按下任何按键都将显示充电页面。

状态模式结构

  1. 上下文 (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。

  2. 状态 (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。

  3. 具体状态 (Concrete States) 会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。

    状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。

  4. 上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。

伪代码

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

使用状态对象更改对象行为的示例。

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

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)

状态模式适合应用场景

如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。

模式建议你将所有特定于状态的代码抽取到一组独立的类中。 这样一来, 你可以在独立于其他状态的情况下添加新状态或修改已有状态, 从而减少维护成本。

如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。

状态模式会将这些条件语句的分支抽取到相应状态类的方法中。 同时, 你还可以清除主要类中与特定状态相关的临时成员变量和帮手方法代码。

当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。

状态模式让你能够生成状态类层次结构, 通过将公用代码抽取到抽象基类中来减少重复。

实现方式

  1. 确定哪些类是上下文。 它可能是包含依赖于状态的代码的已有类; 如果特定于状态的代码分散在多个类中, 那么它可能是一个新的类。

  2. 声明状态接口。 虽然你可能会需要完全复制上下文中声明的所有方法, 但最好是仅把关注点放在那些可能包含特定于状态的行为的方法上。

  3. 为每个实际状态创建一个继承于状态接口的类。 然后检查上下文中的方法并将与特定状态相关的所有代码抽取到新建的类中。

    在将代码移动到状态类的过程中, 你可能会发现它依赖于上下文中的一些私有成员。 你可以采用以下几种变通方式:

    • 将这些成员变量或方法设为公有。
    • 将需要抽取的上下文行为更改为上下文中的公有方法, 然后在状态类中调用。 这种方式简陋却便捷, 你可以稍后再对其进行修补。
    • 将状态类嵌套在上下文类中。 这种方式需要你所使用的编程语言支持嵌套类。
  4. 在上下文类中添加一个状态接口类型的引用成员变量, 以及一个用于修改该成员变量值的公有设置器。

  5. 再次检查上下文中的方法, 将空的条件语句替换为相应的状态对象方法。

  6. 为切换上下文状态, 你需要创建某个状态类实例并将其传递给上下文。 你可以在上下文、 各种状态或客户端中完成这项工作。 无论在何处完成这项工作, 该类都将依赖于其所实例化的具体类。

状态模式优缺点

优点

  • s单一职责原则。 将与特定状态相关的代码放在单独的类中。
  • 开闭原则。 无需修改已有状态类和上下文就能引入新状态。
  • 通过消除臃肿的状态机条件语句简化上下文代码。

缺点

  • 如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。

与其他模式的关系

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

代码示例

状态是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为。

该模式将与状态相关的行为抽取到独立的状态类中, 让原对象将工作委派给这些类的实例, 而不是自行进行处理。

在 C# 中使用模式

复杂度: ★☆☆

流行度: ★★☆

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

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

概念示例

本例说明了状态设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

Program.cs: 概念示例

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
using System;

namespace RefactoringGuru.DesignPatterns.State.Conceptual
{
// The Context defines the interface of interest to clients. It also
// maintains a reference to an instance of a State subclass, which
// represents the current state of the Context.
class Context
{
// A reference to the current state of the Context.
private State _state = null;

public Context(State state)
{
this.TransitionTo(state);
}

// The Context allows changing the State object at runtime.
public void TransitionTo(State state)
{
Console.WriteLine($"Context: Transition to {state.GetType().Name}.");
this._state = state;
this._state.SetContext(this);
}

// The Context delegates part of its behavior to the current State
// object.
public void Request1()
{
this._state.Handle1();
}

public void Request2()
{
this._state.Handle2();
}
}

// The base State class declares methods that all Concrete State should
// implement and also provides a backreference to the Context object,
// associated with the State. This backreference can be used by States to
// transition the Context to another State.
abstract class State
{
protected Context _context;

public void SetContext(Context context)
{
this._context = context;
}

public abstract void Handle1();

public abstract void Handle2();
}

// Concrete States implement various behaviors, associated with a state of
// the Context.
class ConcreteStateA : State
{
public override void Handle1()
{
Console.WriteLine("ConcreteStateA handles request1.");
Console.WriteLine("ConcreteStateA wants to change the state of the context.");
this._context.TransitionTo(new ConcreteStateB());
}

public override void Handle2()
{
Console.WriteLine("ConcreteStateA handles request2.");
}
}

class ConcreteStateB : State
{
public override void Handle1()
{
Console.Write("ConcreteStateB handles request1.");
}

public override void Handle2()
{
Console.WriteLine("ConcreteStateB handles request2.");
Console.WriteLine("ConcreteStateB wants to change the state of the context.");
this._context.TransitionTo(new ConcreteStateA());
}
}

class Program
{
static void Main(string[] args)
{
// The client code.
var context = new Context(new ConcreteStateA());
context.Request1();
context.Request2();
}
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
Context: Transition to ConcreteStateA.
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to ConcreteStateB.
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to ConcreteStateA.

在 C++ 中使用模式

复杂度: ★☆☆

流行度: ★★☆

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

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

概念示例

本例说明了状态设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

main.cc: 概念示例

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
118
119
#include <iostream>
#include <typeinfo>
/**
* The base State class declares methods that all Concrete State should
* implement and also provides a backreference to the Context object, associated
* with the State. This backreference can be used by States to transition the
* Context to another State.
*/

class Context;

class State {
/**
* @var Context
*/
protected:
Context *context_;

public:
virtual ~State() {
}

void set_context(Context *context) {
this->context_ = context;
}

virtual void Handle1() = 0;
virtual void Handle2() = 0;
};

/**
* The Context defines the interface of interest to clients. It also maintains a
* reference to an instance of a State subclass, which represents the current
* state of the Context.
*/
class Context {
/**
* @var State A reference to the current state of the Context.
*/
private:
State *state_;

public:
Context(State *state) : state_(nullptr) {
this->TransitionTo(state);
}
~Context() {
delete state_;
}
/**
* The Context allows changing the State object at runtime.
*/
void TransitionTo(State *state) {
std::cout << "Context: Transition to " << typeid(*state).name() << ".\n";
if (this->state_ != nullptr)
delete this->state_;
this->state_ = state;
this->state_->set_context(this);
}
/**
* The Context delegates part of its behavior to the current State object.
*/
void Request1() {
this->state_->Handle1();
}
void Request2() {
this->state_->Handle2();
}
};

/**
* Concrete States implement various behaviors, associated with a state of the
* Context.
*/

class ConcreteStateA : public State {
public:
void Handle1() override;

void Handle2() override {
std::cout << "ConcreteStateA handles request2.\n";
}
};

class ConcreteStateB : public State {
public:
void Handle1() override {
std::cout << "ConcreteStateB handles request1.\n";
}
void Handle2() override {
std::cout << "ConcreteStateB handles request2.\n";
std::cout << "ConcreteStateB wants to change the state of the context.\n";
this->context_->TransitionTo(new ConcreteStateA);
}
};

void ConcreteStateA::Handle1() {
{
std::cout << "ConcreteStateA handles request1.\n";
std::cout << "ConcreteStateA wants to change the state of the context.\n";

this->context_->TransitionTo(new ConcreteStateB);
}
}

/**
* The client code.
*/
void ClientCode() {
Context *context = new Context(new ConcreteStateA);
context->Request1();
context->Request2();
delete context;
}

int main() {
ClientCode();
return 0;
}

Output.txt: 执行结果

1
2
3
4
5
6
7
Context: Transition to 14ConcreteStateA.
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to 14ConcreteStateB.
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to 14ConcreteStateA.

在 Java 中使用模式

复杂度: ★☆☆

流行度: ★★☆

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

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

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

媒体播放器的接口

在本例中, 状态模式允许媒体播放器根据当前的回放状态进行不同的控制行为。 播放器主类包含一个指向状态对象的引用, 它将完成播放器的绝大部分工作。 某些行为可能会用一个状态对象替换另一个状态对象, 改变播放器对用户交互的回应方式。

states

states/State.java: 通用状态接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package refactoring_guru.state.example.states;

import refactoring_guru.state.example.ui.Player;

/**
* Common interface for all states.
*/
public abstract class State {
Player player;

/**
* Context passes itself through the state constructor. This may help a
* state to fetch some useful context data if needed.
*/
State(Player player) {
this.player = player;
}

public abstract String onLock();
public abstract String onPlay();
public abstract String onNext();
public abstract String onPrevious();
}

states/LockedState.java

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
package refactoring_guru.state.example.states;

import refactoring_guru.state.example.ui.Player;

/**
* Concrete states provide the special implementation for all interface methods.
*/
public class LockedState extends State {

LockedState(Player player) {
super(player);
player.setPlaying(false);
}

@Override
public String onLock() {
if (player.isPlaying()) {
player.changeState(new ReadyState(player));
return "Stop playing";
} else {
return "Locked...";
}
}

@Override
public String onPlay() {
player.changeState(new ReadyState(player));
return "Ready";
}

@Override
public String onNext() {
return "Locked...";
}

@Override
public String onPrevious() {
return "Locked...";
}
}

states/ReadyState.java

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
package refactoring_guru.state.example.states;

import refactoring_guru.state.example.ui.Player;

/**
* They can also trigger state transitions in the context.
*/
public class ReadyState extends State {

public ReadyState(Player player) {
super(player);
}

@Override
public String onLock() {
player.changeState(new LockedState(player));
return "Locked...";
}

@Override
public String onPlay() {
String action = player.startPlayback();
player.changeState(new PlayingState(player));
return action;
}

@Override
public String onNext() {
return "Locked...";
}

@Override
public String onPrevious() {
return "Locked...";
}
}

states/PlayingState.java

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
package refactoring_guru.state.example.states;

import refactoring_guru.state.example.ui.Player;

public class PlayingState extends State {

PlayingState(Player player) {
super(player);
}

@Override
public String onLock() {
player.changeState(new LockedState(player));
player.setCurrentTrackAfterStop();
return "Stop playing";
}

@Override
public String onPlay() {
player.changeState(new ReadyState(player));
return "Paused...";
}

@Override
public String onNext() {
return player.nextTrack();
}

@Override
public String onPrevious() {
return player.previousTrack();
}
}

ui

ui/Player.java: 播放器的主要代码

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
package refactoring_guru.state.example.ui;

import refactoring_guru.state.example.states.ReadyState;
import refactoring_guru.state.example.states.State;

import java.util.ArrayList;
import java.util.List;

public class Player {
private State state;
private boolean playing = false;
private List<String> playlist = new ArrayList<>();
private int currentTrack = 0;

public Player() {
this.state = new ReadyState(this);
setPlaying(true);
for (int i = 1; i <= 12; i++) {
playlist.add("Track " + i);
}
}

public void changeState(State state) {
this.state = state;
}

public State getState() {
return state;
}

public void setPlaying(boolean playing) {
this.playing = playing;
}

public boolean isPlaying() {
return playing;
}

public String startPlayback() {
return "Playing " + playlist.get(currentTrack);
}

public String nextTrack() {
currentTrack++;
if (currentTrack > playlist.size() - 1) {
currentTrack = 0;
}
return "Playing " + playlist.get(currentTrack);
}

public String previousTrack() {
currentTrack--;
if (currentTrack < 0) {
currentTrack = playlist.size() - 1;
}
return "Playing " + playlist.get(currentTrack);
}

public void setCurrentTrackAfterStop() {
this.currentTrack = 0;
}
}

ui/UI.java: 播放器的 GUI

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
package refactoring_guru.state.example.ui;

import javax.swing.*;
import java.awt.*;

public class UI {
private Player player;
private static JTextField textField = new JTextField();

public UI(Player player) {
this.player = player;
}

public void init() {
JFrame frame = new JFrame("Test player");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel context = new JPanel();
context.setLayout(new BoxLayout(context, BoxLayout.Y_AXIS));
frame.getContentPane().add(context);
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
context.add(textField);
context.add(buttons);

// Context delegates handling user's input to a state object. Naturally,
// the outcome will depend on what state is currently active, since all
// states can handle the input differently.
JButton play = new JButton("Play");
play.addActionListener(e -> textField.setText(player.getState().onPlay()));
JButton stop = new JButton("Stop");
stop.addActionListener(e -> textField.setText(player.getState().onLock()));
JButton next = new JButton("Next");
next.addActionListener(e -> textField.setText(player.getState().onNext()));
JButton prev = new JButton("Prev");
prev.addActionListener(e -> textField.setText(player.getState().onPrevious()));
frame.setVisible(true);
frame.setSize(300, 100);
buttons.add(play);
buttons.add(stop);
buttons.add(next);
buttons.add(prev);
}
}

Demo.java: 初始化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package refactoring_guru.state.example;

import refactoring_guru.state.example.ui.Player;
import refactoring_guru.state.example.ui.UI;

/**
* Demo class. Everything comes together here.
*/
public class Demo {
public static void main(String[] args) {
Player player = new Player();
UI ui = new UI(player);
ui.init();
}
}

OutputDemo.png: 屏幕截图

在 PHP 中使用模式

复杂度: ★☆☆

流行度: ★★☆

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

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

概念示例

本例说明了状态设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 PHP 应用案例。

index.php: 概念示例

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
<?php

namespace RefactoringGuru\State\Conceptual;

/**
* The Context defines the interface of interest to clients. It also maintains a
* reference to an instance of a State subclass, which represents the current
* state of the Context.
*/
class Context
{
/**
* @var State A reference to the current state of the Context.
*/
private $state;

public function __construct(State $state)
{
$this->transitionTo($state);
}

/**
* The Context allows changing the State object at runtime.
*/
public function transitionTo(State $state): void
{
echo "Context: Transition to " . get_class($state) . ".\n";
$this->state = $state;
$this->state->setContext($this);
}

/**
* The Context delegates part of its behavior to the current State object.
*/
public function request1(): void
{
$this->state->handle1();
}

public function request2(): void
{
$this->state->handle2();
}
}

/**
* The base State class declares methods that all Concrete State should
* implement and also provides a backreference to the Context object, associated
* with the State. This backreference can be used by States to transition the
* Context to another State.
*/
abstract class State
{
/**
* @var Context
*/
protected $context;

public function setContext(Context $context)
{
$this->context = $context;
}

abstract public function handle1(): void;

abstract public function handle2(): void;
}

/**
* Concrete States implement various behaviors, associated with a state of the
* Context.
*/
class ConcreteStateA extends State
{
public function handle1(): void
{
echo "ConcreteStateA handles request1.\n";
echo "ConcreteStateA wants to change the state of the context.\n";
$this->context->transitionTo(new ConcreteStateB());
}

public function handle2(): void
{
echo "ConcreteStateA handles request2.\n";
}
}

class ConcreteStateB extends State
{
public function handle1(): void
{
echo "ConcreteStateB handles request1.\n";
}

public function handle2(): void
{
echo "ConcreteStateB handles request2.\n";
echo "ConcreteStateB wants to change the state of the context.\n";
$this->context->transitionTo(new ConcreteStateA());
}
}

/**
* The client code.
*/
$context = new Context(new ConcreteStateA());
$context->request1();
$context->request2();

Output.txt: 执行结果

1
2
3
4
5
6
7
Context: Transition to RefactoringGuru\State\Conceptual\ConcreteStateA.
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to RefactoringGuru\State\Conceptual\ConcreteStateB.
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to RefactoringGuru\State\Conceptual\ConcreteStateA.

真实世界示例

尽管状态机和状态模式自身在开发者中非常流行, 但是我却想不到任何 PHP 中的实际应用来作为该模式在真实世界的示例。

但是, 如果你曾在项目中使用过该模式, 欢迎通过论坛或邮件support@refactoring.guru分享你的经验。 谢谢!

在 Python 中使用模式

复杂度: ★☆☆

流行度: ★★☆

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

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

概念示例

本例说明了状态设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

main.py: 概念示例

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
from __future__ import annotations
from abc import ABC, abstractmethod


class Context:
"""
The Context defines the interface of interest to clients. It also maintains
a reference to an instance of a State subclass, which represents the current
state of the Context.
"""

_state = None
"""
A reference to the current state of the Context.
"""

def __init__(self, state: State) -> None:
self.transition_to(state)

def transition_to(self, state: State):
"""
The Context allows changing the State object at runtime.
"""

print(f"Context: Transition to {type(state).__name__}")
self._state = state
self._state.context = self

"""
The Context delegates part of its behavior to the current State object.
"""

def request1(self):
self._state.handle1()

def request2(self):
self._state.handle2()


class State(ABC):
"""
The base State class declares methods that all Concrete State should
implement and also provides a backreference to the Context object,
associated with the State. This backreference can be used by States to
transition the Context to another State.
"""

@property
def context(self) -> Context:
return self._context

@context.setter
def context(self, context: Context) -> None:
self._context = context

@abstractmethod
def handle1(self) -> None:
pass

@abstractmethod
def handle2(self) -> None:
pass


"""
Concrete States implement various behaviors, associated with a state of the
Context.
"""


class ConcreteStateA(State):
def handle1(self) -> None:
print("ConcreteStateA handles request1.")
print("ConcreteStateA wants to change the state of the context.")
self.context.transition_to(ConcreteStateB())

def handle2(self) -> None:
print("ConcreteStateA handles request2.")


class ConcreteStateB(State):
def handle1(self) -> None:
print("ConcreteStateB handles request1.")

def handle2(self) -> None:
print("ConcreteStateB handles request2.")
print("ConcreteStateB wants to change the state of the context.")
self.context.transition_to(ConcreteStateA())


if __name__ == "__main__":
# The client code.

context = Context(ConcreteStateA())
context.request1()
context.request2()

Output.txt: 执行结果

1
2
3
4
5
6
7
Context: Transition to ConcreteStateA
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to ConcreteStateB
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to ConcreteStateA

在 Ruby 中使用模式

复杂度: ★☆☆

流行度: ★★☆

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

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

概念示例

本例说明了状态设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

main.rb: 概念示例

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
# The Context defines the interface of interest to clients. It also maintains a
# reference to an instance of a State subclass, which represents the current
# state of the Context.
class Context
# A reference to the current state of the Context.
attr_accessor :state
private :state

# @param [State] state
def initialize(state)
transition_to(state)
end

# The Context allows changing the State object at runtime.
def transition_to(state)
puts "Context: Transition to #{state.class}"
@state = state
@state.context = self
end

# The Context delegates part of its behavior to the current State object.

def request1
@state.handle1
end

def request2
@state.handle2
end
end

# The base State class declares methods that all Concrete State should implement
# and also provides a backreference to the Context object, associated with the
# State. This backreference can be used by States to transition the Context to
# another State.
class State
attr_accessor :context

# @abstract
def handle1
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end

# @abstract
def handle2
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end

# Concrete States implement various behaviors, associated with a state of the
# Context.

class ConcreteStateA < State
def handle1
puts 'ConcreteStateA handles request1.'
puts 'ConcreteStateA wants to change the state of the context.'
@context.transition_to(ConcreteStateB.new)
end

def handle2
puts 'ConcreteStateA handles request2.'
end
end

class ConcreteStateB < State
def handle1
puts 'ConcreteStateB handles request1.'
end

def handle2
puts 'ConcreteStateB handles request2.'
puts 'ConcreteStateB wants to change the state of the context.'
@context.transition_to(ConcreteStateA.new)
end
end

# The client code.

context = Context.new(ConcreteStateA.new)
context.request1
context.request2

output.txt: 执行结果

1
2
3
4
5
6
7
Context: Transition to ConcreteStateA
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to ConcreteStateB
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to ConcreteStateA

在 Swift 中使用模式

复杂度: ★☆☆

流行度: ★★☆

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

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

概念示例

本例说明了状态设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。

Example.swift: 概念示例

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
import XCTest

/// The Context defines the interface of interest to clients. It also maintains
/// a reference to an instance of a State subclass, which represents the current
/// state of the Context.
class Context {

/// A reference to the current state of the Context.
private var state: State

init(_ state: State) {
self.state = state
transitionTo(state: state)
}

/// The Context allows changing the State object at runtime.
func transitionTo(state: State) {
print("Context: Transition to " + String(describing: state))
self.state = state
self.state.update(context: self)
}

/// The Context delegates part of its behavior to the current State object.
func request1() {
state.handle1()
}

func request2() {
state.handle2()
}
}

/// The base State class declares methods that all Concrete State should
/// implement and also provides a backreference to the Context object,
/// associated with the State. This backreference can be used by States to
/// transition the Context to another State.
protocol State: class {

func update(context: Context)

func handle1()
func handle2()
}

class BaseState: State {

private(set) weak var context: Context?

func update(context: Context) {
self.context = context
}

func handle1() {}
func handle2() {}
}

/// Concrete States implement various behaviors, associated with a state of the
/// Context.
class ConcreteStateA: BaseState {

override func handle1() {
print("ConcreteStateA handles request1.")
print("ConcreteStateA wants to change the state of the context.\n")
context?.transitionTo(state: ConcreteStateB())
}

override func handle2() {
print("ConcreteStateA handles request2.\n")
}
}

class ConcreteStateB: BaseState {

override func handle1() {
print("ConcreteStateB handles request1.\n")
}

override func handle2() {
print("ConcreteStateB handles request2.")
print("ConcreteStateB wants to change the state of the context.\n")
context?.transitionTo(state: ConcreteStateA())
}
}

/// Let's see how it all works together.
class StateConceptual: XCTestCase {

func test() {
let context = Context(ConcreteStateA())
context.request1()
context.request2()
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
Context: Transition to StateConceptual.ConcreteStateA
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.

Context: Transition to StateConceptual.ConcreteStateB
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.

Context: Transition to StateConceptual.ConcreteStateA

真实世界示例

Example.swift: 真实世界示例

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import XCTest

class StateRealWorld: XCTestCase {

func test() {

print("Client: I'm starting working with a location tracker")
let tracker = LocationTracker()

print()
tracker.startTracking()

print()
tracker.pauseTracking(for: 2)

print()
tracker.makeCheckIn()

print()
tracker.findMyChildren()

print()
tracker.stopTracking()
}
}

class LocationTracker {

/// Location tracking is enabled by default
private lazy var trackingState: TrackingState = EnabledTrackingState(tracker: self)

func startTracking() {
trackingState.startTracking()
}

func stopTracking() {
trackingState.stopTracking()
}

func pauseTracking(for time: TimeInterval) {
trackingState.pauseTracking(for: time)
}

func makeCheckIn() {
trackingState.makeCheckIn()
}

func findMyChildren() {
trackingState.findMyChildren()
}

func update(state: TrackingState) {
trackingState = state
}
}

protocol TrackingState {

func startTracking()
func stopTracking()
func pauseTracking(for time: TimeInterval)

func makeCheckIn()
func findMyChildren()
}

class EnabledTrackingState: TrackingState {

private weak var tracker: LocationTracker?

init(tracker: LocationTracker?) {
self.tracker = tracker
}

func startTracking() {
print("EnabledTrackingState: startTracking is invoked")
print("EnabledTrackingState: tracking location....1")
print("EnabledTrackingState: tracking location....2")
print("EnabledTrackingState: tracking location....3")
}

func stopTracking() {
print("EnabledTrackingState: Received 'stop tracking'")
print("EnabledTrackingState: Changing state to 'disabled'...")
tracker?.update(state: DisabledTrackingState(tracker: tracker))
tracker?.stopTracking()
}

func pauseTracking(for time: TimeInterval) {
print("EnabledTrackingState: Received 'pause tracking' for \(time) seconds")
print("EnabledTrackingState: Changing state to 'disabled'...")
tracker?.update(state: DisabledTrackingState(tracker: tracker))
tracker?.pauseTracking(for: time)
}

func makeCheckIn() {
print("EnabledTrackingState: performing check-in at the current location")
}

func findMyChildren() {
print("EnabledTrackingState: searching for children...")
}
}

class DisabledTrackingState: TrackingState {

private weak var tracker: LocationTracker?

init(tracker: LocationTracker?) {
self.tracker = tracker
}

func startTracking() {
print("DisabledTrackingState: Received 'start tracking'")
print("DisabledTrackingState: Changing state to 'enabled'...")
tracker?.update(state: EnabledTrackingState(tracker: tracker))
}

func pauseTracking(for time: TimeInterval) {
print("DisabledTrackingState: Pause tracking for \(time) seconds")

for i in 0...Int(time) {
print("DisabledTrackingState: pause...\(i)")
}

print("DisabledTrackingState: Time is over")
print("DisabledTrackingState: Returing to 'enabled state'...\n")
self.tracker?.update(state: EnabledTrackingState(tracker: self.tracker))
self.tracker?.startTracking()
}

func stopTracking() {
print("DisabledTrackingState: Received 'stop tracking'")
print("DisabledTrackingState: Do nothing...")
}

func makeCheckIn() {
print("DisabledTrackingState: Received 'make check-in'")
print("DisabledTrackingState: Changing state to 'enabled'...")
tracker?.update(state: EnabledTrackingState(tracker: tracker))
tracker?.makeCheckIn()
}

func findMyChildren() {
print("DisabledTrackingState: Received 'find my children'")
print("DisabledTrackingState: Changing state to 'enabled'...")
tracker?.update(state: EnabledTrackingState(tracker: tracker))
tracker?.findMyChildren()
}
}

Output.txt: 执行结果

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
Client: I'm starting working with a location tracker

EnabledTrackingState: startTracking is invoked
EnabledTrackingState: tracking location....1
EnabledTrackingState: tracking location....2
EnabledTrackingState: tracking location....3

EnabledTrackingState: Received 'pause tracking' for 2.0 seconds
EnabledTrackingState: Changing state to 'disabled'...
DisabledTrackingState: Pause tracking for 2.0 seconds
DisabledTrackingState: pause...0
DisabledTrackingState: pause...1
DisabledTrackingState: pause...2
DisabledTrackingState: Time is over
DisabledTrackingState: Returing to 'enabled state'...

EnabledTrackingState: startTracking is invoked
EnabledTrackingState: tracking location....1
EnabledTrackingState: tracking location....2
EnabledTrackingState: tracking location....3

EnabledTrackingState: performing check-in at the current location

EnabledTrackingState: searching for children...

EnabledTrackingState: Received 'stop tracking'
EnabledTrackingState: Changing state to 'disabled'...
DisabledTrackingState: Received 'stop tracking'
DisabledTrackingState: Do nothing...

在 TypeScript 中使用模式

复杂度: ★☆☆

流行度: ★★☆

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

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

概念示例

本例说明了状态设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

index.ts: 概念示例

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
/**
* The Context defines the interface of interest to clients. It also maintains a
* reference to an instance of a State subclass, which represents the current
* state of the Context.
*/
class Context {
/**
* @type {State} A reference to the current state of the Context.
*/
private state: State;

constructor(state: State) {
this.transitionTo(state);
}

/**
* The Context allows changing the State object at runtime.
*/
public transitionTo(state: State): void {
console.log(`Context: Transition to ${(<any>state).constructor.name}.`);
this.state = state;
this.state.setContext(this);
}

/**
* The Context delegates part of its behavior to the current State object.
*/
public request1(): void {
this.state.handle1();
}

public request2(): void {
this.state.handle2();
}
}

/**
* The base State class declares methods that all Concrete State should
* implement and also provides a backreference to the Context object, associated
* with the State. This backreference can be used by States to transition the
* Context to another State.
*/
abstract class State {
protected context: Context;

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

public abstract handle1(): void;

public abstract handle2(): void;
}

/**
* Concrete States implement various behaviors, associated with a state of the
* Context.
*/
class ConcreteStateA extends State {
public handle1(): void {
console.log('ConcreteStateA handles request1.');
console.log('ConcreteStateA wants to change the state of the context.');
this.context.transitionTo(new ConcreteStateB());
}

public handle2(): void {
console.log('ConcreteStateA handles request2.');
}
}

class ConcreteStateB extends State {
public handle1(): void {
console.log('ConcreteStateB handles request1.');
}

public handle2(): void {
console.log('ConcreteStateB handles request2.');
console.log('ConcreteStateB wants to change the state of the context.');
this.context.transitionTo(new ConcreteStateA());
}
}

/**
* The client code.
*/
const context = new Context(new ConcreteStateA());
context.request1();
context.request2();

Output.txt: 执行结果

1
2
3
4
5
6
7
Context: Transition to ConcreteStateA.
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to ConcreteStateB.
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to ConcreteStateA.

概念示例

让我们在一台自动售货机上使用状态设计模式。 为简单起见, 让我们假设自动售货机仅会销售一种类型的商品。 同时, 依然为了简单起见, 我们假设自动售货机可处于 4 种不同的状态中:

  • 有商品 (has­Item)
  • 无商品 (no­Item)
  • 商品已请求 (item­Requested)
  • 收到纸币 (has­Money)

同时, 自动售货机也会有不同的操作。 再一次的, 为了简单起见, 我们假设其只会执行 4 种操作:

  • 选择商品
  • 添加商品
  • 插入纸币
  • 提供商品

当对象可以处于许多不同的状态中时应使用状态设计模式, 同时根据传入请求的不同, 对象需要变更其当前状态。

在我们的例子中, 自动售货机可以有多种不同的状态, 同时会在这些状态之间持续不断地互相转换。 我们假设自动售货机处于 商品已请求状态中。 在 “插入纸币” 的操作发生后, 机器将自动转换至 收到纸币状态。

根据其当前状态, 机器可就相同请求采取不同的行为。 例如, 如果用户想要购买一件商品, 机器将在 有商品状态时继续操作, 而在 无商品状态时拒绝操作。

自动售货机的代码不会被这一逻辑污染; 所有依赖于状态的代码都存在于各自的状态实现中。

vendingMachine.go: 背景

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
package main

import "fmt"

type vendingMachine struct {
hasItem state
itemRequested state
hasMoney state
noItem state

currentState state

itemCount int
itemPrice int
}

func newVendingMachine(itemCount, itemPrice int) *vendingMachine {
v := &vendingMachine{
itemCount: itemCount,
itemPrice: itemPrice,
}
hasItemState := &hasItemState{
vendingMachine: v,
}
itemRequestedState := &itemRequestedState{
vendingMachine: v,
}
hasMoneyState := &hasMoneyState{
vendingMachine: v,
}
noItemState := &noItemState{
vendingMachine: v,
}

v.setState(hasItemState)
v.hasItem = hasItemState
v.itemRequested = itemRequestedState
v.hasMoney = hasMoneyState
v.noItem = noItemState
return v
}

func (v *vendingMachine) requestItem() error {
return v.currentState.requestItem()
}

func (v *vendingMachine) addItem(count int) error {
return v.currentState.addItem(count)
}

func (v *vendingMachine) insertMoney(money int) error {
return v.currentState.insertMoney(money)
}

func (v *vendingMachine) dispenseItem() error {
return v.currentState.dispenseItem()
}

func (v *vendingMachine) setState(s state) {
v.currentState = s
}

func (v *vendingMachine) incrementItemCount(count int) {
fmt.Printf("Adding %d items\n", count)
v.itemCount = v.itemCount + count
}

state.go: 状态接口

1
2
3
4
5
6
7
8
package main

type state interface {
addItem(int) error
requestItem() error
insertMoney(money int) error
dispenseItem() error
}

noItemState.go: 具体状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

type noItemState struct {
vendingMachine *vendingMachine
}

func (i *noItemState) requestItem() error {
return fmt.Errorf("Item out of stock")
}

func (i *noItemState) addItem(count int) error {
i.vendingMachine.incrementItemCount(count)
i.vendingMachine.setState(i.vendingMachine.hasItem)
return nil
}

func (i *noItemState) insertMoney(money int) error {
return fmt.Errorf("Item out of stock")
}
func (i *noItemState) dispenseItem() error {
return fmt.Errorf("Item out of stock")
}

hasItemState.go: 具体状态

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
package main

import "fmt"

type hasItemState struct {
vendingMachine *vendingMachine
}

func (i *hasItemState) requestItem() error {
if i.vendingMachine.itemCount == 0 {
i.vendingMachine.setState(i.vendingMachine.noItem)
return fmt.Errorf("No item present")
}
fmt.Printf("Item requestd\n")
i.vendingMachine.setState(i.vendingMachine.itemRequested)
return nil
}

func (i *hasItemState) addItem(count int) error {
fmt.Printf("%d items added\n", count)
i.vendingMachine.incrementItemCount(count)
return nil
}

func (i *hasItemState) insertMoney(money int) error {
return fmt.Errorf("Please select item first")
}
func (i *hasItemState) dispenseItem() error {
return fmt.Errorf("Please select item first")
}

itemRequestedState.go: 具体状态

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
package main

import "fmt"

type itemRequestedState struct {
vendingMachine *vendingMachine
}

func (i *itemRequestedState) requestItem() error {
return fmt.Errorf("Item already requested")
}

func (i *itemRequestedState) addItem(count int) error {
return fmt.Errorf("Item Dispense in progress")
}

func (i *itemRequestedState) insertMoney(money int) error {
if money < i.vendingMachine.itemPrice {
fmt.Errorf("Inserted money is less. Please insert %d", i.vendingMachine.itemPrice)
}
fmt.Println("Money entered is ok")
i.vendingMachine.setState(i.vendingMachine.hasMoney)
return nil
}
func (i *itemRequestedState) dispenseItem() error {
return fmt.Errorf("Please insert money first")
}

hasMoneyState.go: 具体状态

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
package main

import "fmt"

type hasMoneyState struct {
vendingMachine *vendingMachine
}

func (i *hasMoneyState) requestItem() error {
return fmt.Errorf("Item dispense in progress")
}

func (i *hasMoneyState) addItem(count int) error {
return fmt.Errorf("Item dispense in progress")
}

func (i *hasMoneyState) insertMoney(money int) error {
return fmt.Errorf("Item out of stock")
}
func (i *hasMoneyState) dispenseItem() error {
fmt.Println("Dispensing Item")
i.vendingMachine.itemCount = i.vendingMachine.itemCount - 1
if i.vendingMachine.itemCount == 0 {
i.vendingMachine.setState(i.vendingMachine.noItem)
} else {
i.vendingMachine.setState(i.vendingMachine.hasItem)
}
return nil
}

main.go: 客户端代码

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
package main

import (
"fmt"
"log"
)

func main() {
vendingMachine := newVendingMachine(1, 10)

err := vendingMachine.requestItem()
if err != nil {
log.Fatalf(err.Error())
}

err = vendingMachine.insertMoney(10)
if err != nil {
log.Fatalf(err.Error())
}

err = vendingMachine.dispenseItem()
if err != nil {
log.Fatalf(err.Error())
}

fmt.Println()

err = vendingMachine.addItem(2)
if err != nil {
log.Fatalf(err.Error())
}

fmt.Println()

err = vendingMachine.requestItem()
if err != nil {
log.Fatalf(err.Error())
}

err = vendingMachine.insertMoney(10)
if err != nil {
log.Fatalf(err.Error())
}

err = vendingMachine.dispenseItem()
if err != nil {
log.Fatalf(err.Error())
}
}

output.txt: 执行结果

1
2
3
4
5
6
7
8
9
Item requestd
Money entered is ok
Dispensing Item

Adding 2 items

Item requestd
Money entered is ok
Dispensing Item

根据: Golang By Example