Dunwu Blog

大道至简,知易行难

设计模式之状态模式

意图

状态模式(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 程序库中一些状态模式的示例:

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

与其他模式的关系

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

参考资料

设计模式之访问者模式

意图

访问者模式(Visitor) 是一种行为设计模式, 它能将算法与其所作用的对象隔离开来。

适用场景

  • 如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。
  • 可使用访问者模式来清理辅助行为的业务逻辑。
  • 当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。

结构

结构说明

img

  1. 访问者 (Visitor) 接口声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。
  2. 具体访问者 (Concrete Visitor) 会为不同的具体元素类实现相同行为的几个不同版本。
  3. 元素 (Element) 接口声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型。
  4. 具体元素 (Concrete Element) 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访问者对象中的合适方法。
  5. 客户端 (Client) 通常会作为集合或其他复杂对象 (例如一个组合树) 的代表。 客户端通常不知晓所有的具体元素类, 因为它们会通过抽象接口与集合中的对象进行交互。

结构代码范式

Visitor : 为该对象结构中 ConcreteElement 的每一个类声明一个 Visit 操作。

1
2
3
4
abstract class Visitor {
public abstract void VisitConcreteElementA(ConcreteElementA elementA);
public abstract void VisitConcreteElementB(ConcreteElementB elementB);
}

ConcreteVisitor : 实现每个由 Visitor 声明的操作。每个操作实现算法的一部分,而该算法片段乃是对应于结构中对象的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ConcreteVisitor1 extends Visitor {
@Override
public void VisitConcreteElementA(ConcreteElementA elementA) {
System.out.println(this.getClass().getName() + " 访问 " + elementA.getClass().getName());
}

@Override
public void VisitConcreteElementB(ConcreteElementB elementB) {
System.out.println(this.getClass().getName() + " 访问 " + elementB.getClass().getName());
}
}

class ConcreteVisitor2 extends Visitor {
@Override
public void VisitConcreteElementA(ConcreteElementA elementA) {
System.out.println(this.getClass().getName() + " 访问 " + elementA.getClass().getName());
}

@Override
public void VisitConcreteElementB(ConcreteElementB elementB) {
System.out.println(this.getClass().getName() + " 访问 " + elementB.getClass().getName());
}
}

Element : 定义一个 Accpet 操作,它以一个访问者为参数。

1
2
3
abstract class Element {
public abstract void Accept(Visitor visitor);
}

ConcreteElement : 实现 Element 声明的 Accept 操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
class ConcreteElementA extends Element {
@Override
public void Accept(Visitor visitor) {
visitor.VisitConcreteElementA(this);
}
}

class ConcreteElementB extends Element {
@Override
public void Accept(Visitor visitor) {
visitor.VisitConcreteElementB(this);
}
}

ObjectStructure : 可以枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ObjectStructure {
private List<Element> elements = new ArrayList<Element>();

public void Attach(Element element) {
elements.add(element);
}

public void Detach(Element element) {
elements.remove(element);
}

public void Accept(Visitor visitor) {
for (Element elem : elements) {
elem.Accept(visitor);
}
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
public class VisitorPattern {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure();
o.Attach(new ConcreteElementA());
o.Attach(new ConcreteElementB());

ConcreteVisitor1 v1 = new ConcreteVisitor1();
ConcreteVisitor2 v2 = new ConcreteVisitor2();

o.Accept(v1);
o.Accept(v2);
}
}

输出

1
2
3
4
ConcreteVisitor1 访问 ConcreteElementA
ConcreteVisitor1 访问 ConcreteElementB
ConcreteVisitor2 访问 ConcreteElementA
ConcreteVisitor2 访问 ConcreteElementB

伪代码

在本例中, 访问者模式为几何图像层次结构添加了对于 XML 文件导出功能的支持。

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
// 元素接口声明了一个`accept(接收)`方法,它会将访问者基础接口作为一个参
// 数。
interface Shape is
method move(x, y)
method draw()
method accept(v: Visitor)

// 每个具体元素类都必须以特定方式实现`accept`方法,使其能调用相应元素类的
// 访问者方法。
class Dot implements Shape is
// ...

// 注意我们正在调用的`visitDot(访问点)`方法与当前类的名称相匹配。
// 这样我们能让访问者知晓与其交互的元素类。
method accept(v: Visitor) is
v.visitDot(this)

class Circle implements Shape is
// ...
method accept(v: Visitor) is
v.visitCircle(this)

class Rectangle implements Shape is
// ...
method accept(v: Visitor) is
v.visitRectangle(this)

class CompoundShape implements Shape is
// ...
method accept(v: Visitor) is
v.visitCompoundShape(this)


// 访问者接口声明了一组与元素类对应的访问方法。访问方法的签名能让访问者准
// 确辨别出与其交互的元素所属的类。
interface Visitor is
method visitDot(d: Dot)
method visitCircle(c: Circle)
method visitRectangle(r: Rectangle)
method visitCompoundShape(cs: CompoundShape)

// 具体访问者实现了同一算法的多个版本,而且该算法能与所有具体类进行交互。
//
// 访问者模式在复杂对象结构(例如组合树)上使用时能发挥最大作用。在这种情
// 况下,它可以存储算法的一些中间状态,并同时在结构中的不同对象上执行访问
// 者方法。这可能会非常有帮助。
class XMLExportVisitor implements Visitor is
method visitDot(d: Dot) is
// 导出点(dot)的 ID 和中心坐标。

method visitCircle(c: Circle) is
// 导出圆(circle)的 ID 、中心坐标和半径。

method visitRectangle(r: Rectangle) is
// 导出长方形(rectangle)的 ID 、左上角坐标、宽和长。

method visitCompoundShape(cs: CompoundShape) is
// 导出图形(shape)的 ID 和其子项目的 ID 列表。


// 客户端代码可在不知晓具体类的情况下在一组元素上运行访问者操作。“接收”操
// 作会将调用定位到访问者对象的相应操作上。
class Application is
field allShapes: array of Shapes

method export() is
exportVisitor = new XMLExportVisitor()

foreach (shape in allShapes) do
shape.accept(exportVisitor)

案例

使用示例: 访问者不是常用的设计模式, 因为它不仅复杂, 应用范围也比较狭窄。

这里是 Java 程序库代码中该模式的一些示例:

与其他模式的关系

参考资料

设计模式之策略模式

意图

策略模式(Strategy) 是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。

适用场景

  • 当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。
  • 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。
  • 如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。
  • 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。

结构

结构说明

img

  1. 上下文 (Context) 维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流。
  2. 策略 (Strategy) 接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。
  3. 具体策略 (Concrete Strategies) 实现了上下文所用算法的各种不同变体。
  4. 当上下文需要运行算法时, 它会在其已连接的策略对象上调用执行方法。 上下文不清楚其所涉及的策略类型与算法的执行方式。
  5. 客户端 (Client) 会创建一个特定策略对象并将其传递给上下文。 上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。

结构代码范式

Strategy : 定义所有算法的公共接口(AlgorithmInterface)。Context 使用这个接口去调用 ConcreteStrategy 定义的具体算法。

1
2
3
abstract class Strategy {
public abstract void AlgorithmInterface();
}

ConcreteStrategy : 实现 Strategy 中的算法接口(AlgorithmInterface)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ConcreteStrategyA extends Strategy {
@Override
public void AlgorithmInterface() {
System.out.println("算法A");
}
}

class ConcreteStrategyB extends Strategy {
@Override
public void AlgorithmInterface() {
System.out.println("算法B");
}
}

class ConcreteStrategyC extends Strategy {
@Override
public void AlgorithmInterface() {
System.out.println("算法C");
}
}

Context : 用一个 ConcreteStrategy 来配置。维护一个对 Strategy 对象的引用。

1
2
3
4
5
6
7
8
9
10
class Context {
Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}

public void ContextInterface() {
strategy.AlgorithmInterface();
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
public class StrategyPattern {
public static void main(String[] args) {
Context context1 = new Context(new ConcreteStrategyA());
context1.ContextInterface();

Context context2 = new Context(new ConcreteStrategyB());
context2.ContextInterface();

Context context3 = new Context(new ConcreteStrategyC());
context3.ContextInterface();
}
}

输出

1
2
3
算法A
算法B
算法C

伪代码

在本例中, 上下文使用了多个策略来执行不同的计算操作。

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
// 策略接口声明了某个算法各个不同版本间所共有的操作。上下文会使用该接口来
// 调用有具体策略定义的算法。
interface Strategy is
method execute(a, b)

// 具体策略会在遵循策略基础接口的情况下实现算法。该接口实现了它们在上下文
// 中的互换性。
class ConcreteStrategyAdd implements Strategy is
method execute(a, b) is
return a + b

class ConcreteStrategySubtract implements Strategy is
method execute(a, b) is
return a - b

class ConcreteStrategyMultiply implements Strategy is
method execute(a, b) is
return a * b

// 上下文定义了客户端关注的接口。
class Context is
// 上下文会维护指向某个策略对象的引用。上下文不知晓策略的具体类。上下
// 文必须通过策略接口来与所有策略进行交互。
private strategy: Strategy

// 上下文通常会通过构造函数来接收策略对象,同时还提供设置器以便在运行
// 时切换策略。
method setStrategy(Strategy strategy) is
this.strategy = strategy

// 上下文会将一些工作委派给策略对象,而不是自行实现不同版本的算法。
method executeStrategy(int a, int b) is
return strategy.execute(a, b)


// 客户端代码会选择具体策略并将其传递给上下文。客户端必须知晓策略之间的差
// 异,才能做出正确的选择。
class ExampleApplication is
method main() is

创建上下文对象。

读取第一个数。
读取最后一个数。
从用户输入中读取期望进行的行为。

if (action == addition) then
context.setStrategy(new ConcreteStrategyAdd())

if (action == subtraction) then
context.setStrategy(new ConcreteStrategySubtract())

if (action == multiplication) then
context.setStrategy(new ConcreteStrategyMultiply())

result = context.executeStrategy(First number, Second number)

打印结果。

案例

使用示例: 策略模式在 Java 代码中很常见。 它经常在各种框架中使用, 能在不扩展类的情况下向用户提供改变其行为的方式。

Java 8 开始支持 lambda 方法, 它可作为一种替代策略模式的简单方式。

这里有一些核心 Java 程序库中策略模式的示例:

识别方法: 策略模式可以通过允许嵌套对象完成实际工作的方法以及允许将该对象替换为不同对象的设置器来识别。

与其他模式的关系

  • 桥接模式状态模式策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。
  • 命令模式策略看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。
    • 你可以使用命令来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 你可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。
    • 另一方面, 策略通常可用于描述完成某件事的不同方式, 让你能够在同一个上下文类中切换算法。
  • 装饰模式可让你更改对象的外表, 策略则让你能够改变其本质。
  • 模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。
  • 状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。

参考资料

设计模式之备忘录模式

意图

备忘录模式(Memento) 是一种行为设计模式, 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。

适用场景

  • 当你需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式。
  • 当直接访问对象的成员变量、 获取器或设置器将导致封装被突破时, 可以使用该模式。

结构

结构说明

基于嵌套类的实现

img

  1. 原发器 (Originator) 类可以生成自身状态的快照, 也可以在需要时通过快照恢复自身状态。

  2. 备忘录 (Memento) 是原发器状态快照的值对象 (value object)。 通常做法是将备忘录设为不可变的, 并通过构造函数一次性传递数据。

  3. 负责人 (Caretaker) 仅知道 “何时” 和 “为何” 捕捉原发器的状态, 以及何时恢复状态。

    负责人通过保存备忘录栈来记录原发器的历史状态。 当原发器需要回溯历史状态时, 负责人将从栈中获取最顶部的备忘录, 并将其传递给原发器的恢复 (restoration) 方法。

  4. 在该实现方法中, 备忘录类将被嵌套在原发器中。 这样原发器就可访问备忘录的成员变量和方法, 即使这些方法被声明为私有。 另一方面, 负责人对于备忘录的成员变量和方法的访问权限非常有限: 它们只能在栈中保存备忘录, 而不能修改其状态。

基于中间接口的实现

另外一种实现方法适用于不支持嵌套类的编程语言 (没错, 我说的就是 PHP)。

img

  1. 在没有嵌套类的情况下, 你可以规定负责人仅可通过明确声明的中间接口与备忘录互动, 该接口仅声明与备忘录元数据相关的方法, 限制其对备忘录成员变量的直接访问权限。
  2. 另一方面, 原发器可以直接与备忘录对象进行交互, 访问备忘录类中声明的成员变量和方法。 这种方式的缺点在于你需要将备忘录的所有成员变量声明为公有。

封装更加严格的实现

如果你不想让其他类有任何机会通过备忘录来访问原发器的状态, 那么还有另一种可用的实现方式。

img

  1. 这种实现方式允许存在多种不同类型的原发器和备忘录。 每种原发器都和其相应的备忘录类进行交互。 原发器和备忘录都不会将其状态暴露给其他类。
  2. 负责人此时被明确禁止修改存储在备忘录中的状态。 但负责人类将独立于原发器, 因为此时恢复方法被定义在了备忘录类中。
  3. 每个备忘录将与创建了自身的原发器连接。 原发器会将自己及状态传递给备忘录的构造函数。 由于这些类之间的紧密联系, 只要原发器定义了合适的设置器 (setter), 备忘录就能恢复其状态。

结构代码范式

Memento : 负责存储 Originator 对象的内部状态,并可以防止 Originator 以外的其他对象访问 Memento。

Memento 有两个接口,Caretaker 只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。
Originator 可以看到一个宽接口,允许它访问返回到先前状态所需的所有数据。

1
2
3
4
5
6
7
8
9
10
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}

public String GetState() {
return state;
}
}

Originator : 负责创建一个备忘录 Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。

Originator 可根据需要决定 Memento 存储 Originator 的哪些内部状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Originator {
private String state;

public void SetState(String state) {
this.state = state;
}
public String GetState() {
return state;
}

public Memento CreateMemento() {
return (new Memento(state));
}

public void SetMemento(Memento memento) {
state = memento.GetState();
}

public void Show() {
System.out.println("State = " + state);
}
}

Caretaker : 负责保存好备忘录 Memento,不能对备忘录的内容进行操作或检查。

1
2
3
4
5
6
7
8
9
10
class Caretaker {
private Memento memento;

public void SetMemento(Memento memento) {
this.memento = memento;
}
public Memento GetMemento() {
return memento;
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MementoPattern {
public static void main(String[] args) {
Originator o = new Originator();
o.SetState("ON");
o.Show();

Caretaker c = new Caretaker();
c.SetMemento(o.CreateMemento());

o.SetState("OFF");
o.Show();

o.SetMemento(c.GetMemento());
o.Show();
}
}

输出

1
2
3
State = ON
State = OFF
State = ON

伪代码

本例结合使用了命令模式与备忘录模式, 可保存复杂文字编辑器的状态快照, 并能在需要时从快照中恢复之前的状态。

img

命令 (command) 对象将作为负责人, 它们会在执行与命令相关的操作前获取编辑器的备忘录。 当用户试图撤销最近的命令时, 编辑器可以使用保存在命令中的备忘录来将自身回滚到之前的状态。

备忘录类没有声明任何公有的成员变量、 获取器 (getter) 和设置器, 因此没有对象可以修改其内容。 备忘录与创建自己的编辑器相连接, 这使得备忘录能够通过编辑器对象的设置器传递数据, 恢复与其相连接的编辑器的状态。 由于备忘录与特定的编辑器对象相连接, 程序可以使用中心化的撤销栈实现对多个独立编辑器窗口的支持。

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
// 原发器中包含了一些可能会随时间变化的重要数据。它还定义了在备忘录中保存
// 自身状态的方法,以及从备忘录中恢复状态的方法。
class Editor is
private field text, curX, curY, selectionWidth

method setText(text) is
this.text = text

method setCursor(x, y) is
this.curX = curX
this.curY = curY

method setSelectionWidth(width) is
this.selectionWidth = width

// 在备忘录中保存当前的状态。
method createSnapshot():Snapshot is
// 备忘录是不可变的对象;因此原发器会将自身状态作为参数传递给备忘
// 录的构造函数。
return new Snapshot(this, text, curX, curY, selectionWidth)

// 备忘录类保存有编辑器的过往状态。
class Snapshot is
private field editor: Editor
private field text, curX, curY, selectionWidth

constructor Snapshot(editor, text, curX, curY, selectionWidth) is
this.editor = editor
this.text = text
this.curX = curX
this.curY = curY
this.selectionWidth = selectionWidth

// 在某一时刻,编辑器之前的状态可以使用备忘录对象来恢复。
method restore() is
editor.setText(text)
editor.setCursor(curX, curY)
editor.setSelectionWidth(selectionWidth)

// 命令对象可作为负责人。在这种情况下,命令会在修改原发器状态之前获取一个
// 备忘录。当需要撤销时,它会从备忘录中恢复原发器的状态。
class Command is
private field backup: Snapshot

method makeBackup() is
backup = editor.createSnapshot()

method undo() is
if (backup != null)
backup.restore()
// ...

与其他模式的关系

  • 你可以同时使用命令模式备忘录模式来实现 “撤销”。 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。
  • 你可以同时使用备忘录迭代器模式来获取当前迭代器的状态, 并且在需要的时候进行回滚。
  • 有时候原型模式可以作为备忘录的一个简化版本, 其条件是你需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。

案例

使用示例: 备忘录的基本原则可通过序列化来实现, 这在 Java 语言中很常见。 尽管备忘录不是生成对象状态快照的唯一或最有效方法, 但它能在保护原始对象的结构不暴露给其他对象的情况下保存对象状态的备份。

下面是核心 Java 程序库中该模式的一些示例:

参考资料

设计模式之职责链模式

意图

职责链模式(Chain Of Responsibility) 是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。
将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

适用场景

  • 当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时, 可以使用责任链模式。
  • 当必须按顺序执行多个处理者时, 可以使用该模式。
  • 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式。

结构

结构说明

img

  1. 处理者 (Handler) 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。

  2. 基础处理者 (Base Handler) 是一个可选的类, 你可以将所有处理者共用的样本代码放置在其中。

    通常情况下, 该类中定义了一个保存对于下个处理者引用的成员变量。 客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。 该类还可以实现默认的处理行为: 确定下个处理者存在后再将请求传递给它。

  3. 具体处理者 (Concrete Handlers) 包含处理请求的实际代码。 每个处理者接收到请求后, 都必须决定是否进行处理, 以及是否沿着链传递请求。

    处理者通常是独立且不可变的, 需要通过构造函数一次性地获得所有必要地数据。

  4. 客户端 (Client) 可根据程序逻辑一次性或者动态地生成链。 值得注意的是, 请求可发送给链上的任意一个处理者, 而非必须是第一个处理者。

结构代码范式

Handler : 定义一个处理请求的接口。(**可选的**)实现设置后继者的方法。

1
2
3
4
5
6
7
8
abstract class Handler {
protected Handler successor;
public void SetSuccesssor(Handler successor) {
this.successor = successor;
}

public abstract void HandlerRequest(int request);
}

ConcreteHandler : 处理它所负责的请求,可以访问它的后继者,如果可处理该请求,就处理之,否则就将请求转发给它的后继者。

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
class ConcreteHandler1 extends Handler {
@Override
public void HandlerRequest(int request) {
if (request >= 0 && request < 10) {
System.out.println("ConcreteHandler1 处理请求 " + request);
} else if (null != successor) {
successor.HandlerRequest(request);
}
}
}

class ConcreteHandler2 extends Handler {
@Override
public void HandlerRequest(int request) {
if (request >= 10 && request < 20) {
System.out.println("ConcreteHandler2 处理请求 " + request);
} else if (null != successor) {
successor.HandlerRequest(request);
}
}
}

class ConcreteHandler3 extends Handler {
@Override
public void HandlerRequest(int request) {
if (request >= 20 && request < 30) {
System.out.println("ConcreteHandler3 处理请求 " + request);
} else if (null != successor) {
successor.HandlerRequest(request);
}
}
}

Client : 需要设置一个职责链的各环节对象串联起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ChainOfResponsibilityPattern {
public static void main(String[] args) {
Handler h1 = new ConcreteHandler1();
Handler h2 = new ConcreteHandler2();
Handler h3 = new ConcreteHandler3();
h1.SetSuccesssor(h2);
h2.SetSuccesssor(h3);

int[] requests = {2, 29, 9, 15, 4, 19};
for (int i : requests) {
h1.HandlerRequest(i);
}
}
}

伪代码

在本例中, 责任链模式负责为活动的 GUI 元素显示上下文帮助信息。

img

应用程序的 GUI  通常为对象树结构。 例如, 负责渲染程序主窗口的 对话框类就是对象树的根节点。 对话框包含 面板 , 而面板可能包含其他面板, 或是 按钮文本框等下层元素。

只要给一个简单的组件指定帮助文本, 它就可显示简短的上下文提示。 但更复杂的组件可自定义上下文帮助文本的显示方式, 例如显示手册摘录内容或在浏览器中打开一个网页。

img

当用户将鼠标指针移动到某个元素并按下 F1键时, 程序检测到指针下的组件并对其发送帮助请求。 该请求不断向上传递到该元素所有的容器, 直至某个元素能够显示帮助信息。

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
// 处理者接口声明了一个创建处理者链的方法。还声明了一个执行请求的方法。
interface ComponentWithContextualHelp is
method showHelp()


// 简单组件的基础类。
abstract class Component implements ComponentWithContextualHelp is
field tooltipText: string

// 组件容器在处理者链中作为“下一个”链接。
protected field container: Container

// 如果组件设定了帮助文字,那它将会显示提示信息。如果组件没有帮助文字
// 且其容器存在,那它会将调用传递给容器。
method showHelp() is
if (tooltipText != null)
// 显示提示信息。
else
container.showHelp()


// 容器可以将简单组件和其他容器作为其子项目。链关系将在这里建立。该类将从
// 其父类处继承 showHelp(显示帮助)的行为。
abstract class Container extends Component is
protected field children: array of Component

method add(child) is
children.add(child)
child.container = this


// 原始组件应该能够使用帮助操作的默认实现...
class Button extends Component is
// ...

// 但复杂组件可能会对默认实现进行重写。如果无法以新的方式来提供帮助文字,
// 那组件总是还能调用基础实现的(参见 Component 类)。
class Panel extends Container is
field modalHelpText: string

method showHelp() is
if (modalHelpText != null)
// 显示包含帮助文字的模态窗口。
else
super.showHelp()

// ...同上...
class Dialog extends Container is
field wikiPageURL: string

method showHelp() is
if (wikiPageURL != null)
// 打开百科帮助页面。
else
super.showHelp()


// 客户端代码。
class Application is
// 每个程序都能以不同方式对链进行配置。
method createUI() is
dialog = new Dialog("预算报告")
dialog.wikiPageURL = "http://..."
panel = new Panel(0, 0, 400, 800)
panel.modalHelpText = "本面板用于..."
ok = new Button(250, 760, 50, 20, "确认")
ok.tooltipText = "这是一个确认按钮..."
cancel = new Button(320, 760, 50, 20, "取消")
// ...
panel.add(ok)
panel.add(cancel)
dialog.add(panel)

// 想象这里会发生什么。
method onF1KeyPress() is
component = this.getComponentAtMouseCoords()
component.showHelp()

与其他模式的关系

  • 责任链模式命令模式中介者模式观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 责任链通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。

  • 责任链的管理者可使用命令模式实现。 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作。

    还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。

  • 责任链装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。

    责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。

案例

使用示例: 责任链模式在 Java 程序中并不常见, 因为它仅在代码与对象链打交道时才能发挥作用。

该模式最流行的使用案例之一是在 GUI 类中将事件向上传递给父组件。 另一个值得注意的使用案例是依次访问过滤器。

下面是该模式在核心 Java 程序库中的一些示例:

识别方法: 该模式可通过一组对象的行为方法间接调用其他对象的相同方法来识别, 而且所有对象都会遵循相同的接口。

参考资料

设计模式之中介者模式

意图

中介者模式(Mediator) 是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。

适用场景

  • 当一些对象和其他对象紧密耦合以致难以对其进行修改时, 可使用中介者模式。
  • 当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式。
  • 如果为了能在不同情景下复用一些基本行为, 导致你需要被迫创建大量组件子类时, 可使用中介者模式。

结构

img

结构说明

  1. 组件 (Component) 是各种包含业务逻辑的类。 每个组件都有一个指向中介者的引用, 该引用被声明为中介者接口类型。 组件不知道中介者实际所属的类, 因此你可通过将其连接到不同的中介者以使其能在其他程序中复用。
  2. 中介者 (Mediator) 接口声明了与组件交流的方法, 但通常仅包括一个通知方法。 组件可将任意上下文 (包括自己的对象) 作为该方法的参数, 只有这样接收组件和发送者类之间才不会耦合。
  3. 具体中介者 (Concrete Mediator) 封装了多种组件间的关系。 具体中介者通常会保存所有组件的引用并对其进行管理, 甚至有时会对其生命周期进行管理。
  4. 组件并不知道其他组件的情况。 如果组件内发生了重要事件, 它只能通知中介者。 中介者收到通知后能轻易地确定发送者, 这或许已足以判断接下来需要触发的组件了。
    • 对于组件来说, 中介者看上去完全就是一个黑箱。 发送者不知道最终会由谁来处理自己的请求, 接收者也不知道最初是谁发出了请求。

结构代码范式

Mediator : 为 Colleague 对象定义一个交流接口。

1
2
3
abstract class Mediator {
public abstract void Send(String message, Colleague colleague);
}

ConcreteMediator : 实现 Mediator 中的交流接口。 这个类中需要了解并维护所有的 colleague 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ConcreteMediator extends Mediator {
private ConcreteColleague1 colleague1;
private ConcreteColleague2 colleague2;

public void setColleague1(ConcreteColleague1 colleague1) {
this.colleague1 = colleague1;
}

public void setColleague2(ConcreteColleague2 colleague2) {
this.colleague2 = colleague2;
}

@Override
public void Send(String message, Colleague colleague) {
if (colleague == colleague1) {
colleague2.Notify(message);
} else if (colleague == colleague2){
colleague1.Notify(message);
} else {
System.out.println("Error!");
}
}
}

Colleague 组 : 每个 Colleague 对象应该知道它的 Mediator 对象,但不知道其他同事对象。它只能联系 Mediator 对象。

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
abstract class Colleague {
protected Mediator mediator;

public Colleague(Mediator mediator) {
this.mediator = mediator;
}

public void Send(String message) {
mediator.Send(message, this);
}

public abstract void Notify(String message);
}

class ConcreteColleague1 extends Colleague {
public ConcreteColleague1(Mediator mediator) {
super(mediator);
}

@Override
public void Notify(String message) {
System.out.println("同事1得到信息:" + message);
}
}

class ConcreteColleague2 extends Colleague {
public ConcreteColleague2(Mediator mediator) {
super(mediator);
}

@Override
public void Notify(String message) {
System.out.println("同事2得到信息:" + message);
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MediatorPattern {
public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator);
ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator);

mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);

colleague1.Send("How are you?");
colleague2.Send("Fine, thank you. And you?");
colleague1.Send("I'm fine. Thankes.");
}
}

输出

1
2
3
同事2得到信息:How are you?
同事1得到信息:Fine, thank you. And you?
同事2得到信息:I'm fine. Thankes.

伪代码

在本例中, 中介者模式可帮助你减少各种 UI 类 (按钮、 复选框和文本标签) 之间的相互依赖关系。

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
// 中介者接口声明了一个能让组件将各种事件通知给中介者的方法。中介者可对这
// 些事件做出响应并将执行工作传递给其他组件。
interface Mediator is
method notify(sender: Component, event: string)


// 具体中介者类可解开各组件之间相互交叉的连接关系并将其转移到中介者中。
class AuthenticationDialog implements Mediator is
private field title: string
private field loginOrRegisterChkBx: Checkbox
private field loginUsername, loginPassword: Textbox
private field registrationUsername, registrationPassword,
registrationEmail: Textbox
private field okBtn, cancelBtn: Button

constructor AuthenticationDialog() is
// 创建所有组件对象并将当前中介者传递给其构造函数以建立连接。

// 当组件中有事件发生时,它会通知中介者。中介者接收到通知后可自行处理,
// 也可将请求传递给另一个组件。
method notify(sender, event) is
if (sender == loginOrRegisterChkBx and event == "check")
if (loginOrRegisterChkBx.checked)
title = "登录"
// 1. 显示登录表单组件。
// 2. 隐藏注册表单组件。
else
title = "注册"
// 1. 显示注册表单组件。
// 2. 隐藏登录表单组件。

if (sender == okBtn && event == "click")
if (loginOrRegister.checked)
// 尝试找到使用登录信息的用户。
if (!found)
// 在登录字段上方显示错误信息。
else
// 1. 使用注册字段中的数据创建用户账号。
// 2. 完成用户登录工作。 …


// 组件会使用中介者接口与中介者进行交互。因此只需将它们与不同的中介者连接
// 起来,你就能在其他情境中使用这些组件了。
class Component is
field dialog: Mediator

constructor Component(dialog) is
this.dialog = dialog

method click() is
dialog.notify(this, "click")

method keypress() is
dialog.notify(this, "keypress")

// 具体组件之间无法进行交流。它们只有一个交流渠道,那就是向中介者发送通知。
class Button extends Component is
// ...

class Textbox extends Component is
// ...

class Checkbox extends Component is
method check() is
dialog.notify(this, "check")
// ...

与其他模式的关系

  • 责任链模式命令模式中介者模式观察者模式用于处理请求发送者和接收者之间的不同连接方式:
    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 外观模式中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。
    • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
    • 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
  • 中介者观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。
    • 中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。
    • 有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。
    • 当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。
    • 假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。

案例

使用示例: 中介者模式在 Java 代码中最常用于帮助程序 GUI 组件之间的通信。 在 MVC 模式中, 控制器是中介者的同义词。

下面是核心 Java 程序库中该模式的一些示例:

参考资料

设计模式之解释器模式

简介

解释器模式 (Interpreter) 定义一个语言,定义它的文法的一种表示。并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

解释器模式是一种行为型模式

img

Context : 包含解释器之外的一些全局信息。

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

class Context {
private String input;
private String output;

public void setInput(String input) {
this.input = input;
}

public String getInput() {
return this.input;
}

public void setOutput(String output) {
this.output = output;
}

public String getOutput() {
return this.output;
}
}

AbstractExpression : 声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。

1
2
3
abstract class AbstractExpression {
public abstract void Interpret(Context context);
}

TerminalExpression : 实现与文法中的终结符相关联的解释操作。实现抽象表达式中所要求的接口,主要是一个 Interprete()方法。

文法中的每一个终结符都有一个具体终结表达式与之对应。

1
2
3
4
5
6
7
class TerminalExpression extends AbstractExpression {
@Override
public void Interpret(Context context) {
context.setOutput("终端" + context.getInput());
System.out.println(context.getInput() + "经过终端解释器解释为:" + context.getOutput());
}
}

NonterminalExpression : 实现与文法中的非终结符相关联的解释操作。对文法中的每一条规则 R1,R2……Rn 都需要一个具体的非终结符表达式类。通过实现抽象表达式的 Interpret 方法实现解释操作。

1
2
3
4
5
6
7
class NonterminalExpression extends AbstractExpression {
@Override
public void Interpret(Context context) {
context.setOutput("非终端" + context.getInput());
System.out.println(context.getInput() + "经过非终端解释器解释为:" + context.getOutput());
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
public class InterpreterPattern {
public static void main(String[] args) {
Context context = new Context();
context.setInput("ABC");

AbstractExpression expression1 = new TerminalExpression();
expression1.Interpret(context);

AbstractExpression expression2 = new NonterminalExpression();
expression2.Interpret(context);
}
}

输出

1
2
ABC经过终端解释器解释为:终端ABC
ABC经过非终端解释器解释为:非终端ABC

实例

场景

参考资料

设计模式之观察者模式

意图

观察者模式(Observer)是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。

适用场景

  • 当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。
  • 当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用。

结构

img

结构说明

  1. 发布者 (Publisher) 会向其他对象发送值得关注的事件。 事件会在发布者自身状态改变或执行特定行为后发生。 发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。
  2. 当新事件发生时, 发送者会遍历订阅列表并调用每个订阅者对象的通知方法。 该方法是在订阅者接口中声明的。
  3. 订阅者 (Subscriber) 接口声明了通知接口。 在绝大多数情况下, 该接口仅包含一个 update更新方法。 该方法可以拥有多个参数, 使发布者能在更新时传递事件的详细信息。
  4. 具体订阅者 (Concrete Subscribers) 可以执行一些操作来回应发布者的通知。 所有具体订阅者类都实现了同样的接口, 因此发布者不需要与具体类相耦合。
  5. 订阅者通常需要一些上下文信息来正确地处理更新。 因此, 发布者通常会将一些上下文数据作为通知方法的参数进行传递。 发布者也可将自身作为参数进行传递, 使订阅者直接获取所需的数据。
  6. 客户端 (Client) 会分别创建发布者和订阅者对象, 然后为订阅者注册发布者更新。

结构代码范式

Subject : 主题类,保存所有订阅此主题的观察者,观察者的 数量是任意的。定义 添加观察者 (Attach)删除观察者 (Detach) 的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
abstract class Subject {
protected String name;
protected String state;
protected List<Observer> observers = new ArrayList<Observer>();

public abstract String getState();
public abstract void setState(String state);
public abstract void Notify();

public Subject(String name) {
this.name = name;
}

public void Attach(Observer observer) {
observers.add(observer);
}

public void Detach(Observer observer) {
observers.remove(observer);
}
}

Observer : 观察者类,定义**更新接口 (Update)**,当收到 Subject 的通知时,Observer 需要同步更新信息。

1
2
3
4
5
6
7
8
9
abstract class Observer {
protected String name;
protected Subject subject;
public Observer(String name, Subject subject) {
this.name = name;
this.subject = subject;
}
public abstract void Update();
}

ConcreteSubject : 具体主题类,存储对于这个主题感兴趣的所有观察者。当内部状态发生变化时,应**通知所有登记的观察者(Notify)**。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ConcreteSubject extends Subject {
public ConcreteSubject(String name) {
super(name);
}

@Override
public String getState() {
return state;
}

@Override
public void setState(String state) {
this.state = state;
}

@Override
public void Notify() {
System.out.println("======= " + this.name + "主题发布新消息 =======");
for (Observer observer : observers) {
observer.Update();
}
}
}

ConcreteObserver : 具体观察者类,实现 Observer 的**更新接口 (Update)**,以便和 Subject 同步状态信息。

1
2
3
4
5
6
7
8
9
10
11
12
class ConcreteObserver extends Observer {
private String state;
public ConcreteObserver(String name, Subject subject) {
super(name, subject);
}

@Override
public void Update() {
state = subject.getState();
System.out.println(this.name + "收到当前状态:" + state);
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ObserverPattern {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject("天气");
ConcreteObserver observer1 = new ConcreteObserver("张三", subject);
ConcreteObserver observer2 = new ConcreteObserver("李四", subject);
ConcreteObserver observer3 = new ConcreteObserver("王五", subject);

subject.Attach(observer1);
subject.Attach(observer2);
subject.Attach(observer3);
subject.setState("今天下雨");
subject.Notify();

subject.Detach(observer2);
subject.setState("明天天晴");
subject.Notify();
}
}

输出

1
2
3
4
5
6
7
======= 天气主题发布新消息 =======
张三收到当前状态:今天下雨
李四收到当前状态:今天下雨
王五收到当前状态:今天下雨
======= 天气主题发布新消息 =======
张三收到当前状态:明天天晴
王五收到当前状态:明天天晴

伪代码

在本例中, 观察者模式允许文本编辑器对象将自身的状态改变通知给其他服务对象。

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
// 发布者基类包含订阅管理代码和通知方法。
class EventManager is
private field listeners: hash map of event types and listeners

method subscribe(eventType, listener) is
listeners.add(eventType, listener)

method unsubscribe(eventType, listener) is
listeners.remove(eventType, listener)

method notify(eventType, data) is
foreach (listener in listeners.of(eventType)) do
listener.update(data)

// 具体发布者包含一些订阅者感兴趣的实际业务逻辑。我们可以从发布者基类中扩
// 展出该类,但在实际情况下并不总能做到,因为具体发布者可能已经是子类了。
// 在这种情况下,你可用组合来修补订阅逻辑,就像我们在这里做的一样。
class Editor is
public field events: EventManager
private field file: File

constructor Editor() is
events = new EventManager()

// 业务逻辑的方法可将变化通知给订阅者。
method openFile(path) is
this.file = new File(path)
events.notify("open", file.name)

method saveFile() is
file.write()
events.notify("save", file.name)

// ...


// 这里是订阅者接口。如果你的编程语言支持函数类型,则可用一组函数来代替整
// 个订阅者的层次结构。
interface EventListener is
method update(filename)

// 具体订阅者会对其注册的发布者所发出的更新消息做出响应。
class LoggingListener implements EventListener is
private field log: File
private field message

constructor LoggingListener(log_filename, message) is
this.log = new File(log_filename)
this.message = message

method update(filename) is
log.write(replace('%s',filename,message))

class EmailAlertsListener implements EventListener is
private field email: string

constructor EmailAlertsListener(email, message) is
this.email = email
this.message = message

method update(filename) is
system.email(email, replace('%s',filename,message))


// 应用程序可在运行时配置发布者和订阅者。
class Application is
method config() is
editor = new Editor()

logger = new LoggingListener(
"/path/to/log.txt",
"有人打开了文件:%s");
editor.events.subscribe("open", logger)

emailAlerts = new EmailAlertsListener(
"admin@example.com",
"有人更改了文件:%s")
editor.events.subscribe("save", emailAlerts)

与其他模式的关系

  • 责任链模式命令模式中介者模式观察者模式用于处理请求发送者和接收者之间的不同连接方式:
    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 中介者观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。
    • 中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。
    • 有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。
    • 当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。
    • 假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。

案例

使用示例: 观察者模式在 Java 代码中很常见, 特别是在 GUI 组件中。 它提供了在不与其他对象所属类耦合的情况下对其事件做出反应的方式。

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

识别方法: 该模式可以通过将对象存储在列表中的订阅方法, 和对于面向该列表中对象的更新方法的调用来识别。

参考资料

设计模式之迭代器模式

意图

迭代器模式(Iterator) 是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。

适用场景

  • 当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。
  • 使用该模式可以减少程序中重复的遍历代码。
  • 如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。

结构

img

结构说明

  1. 迭代器 (Iterator) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。
  2. 具体迭代器 (Concrete Iterators) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。
  3. 集合 (Collection) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。
  4. 具体集合 (Concrete Collections) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。
  5. 客户端 (Client) 通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。
    • 客户端通常不会自行创建迭代器, 而是会从集合中获取。 但在特定情况下, 客户端可以直接创建一个迭代器 (例如当客户端需要自定义特殊迭代器时)。

结构代码范式

Iterator : 定义访问元素的接口。

1
2
3
4
5
6
interface Iterator {
public Object first();
public Object next();
public boolean isDone();
public Object currentItem();
}

ConcreteIterator : 实现 Iterator 接口。记录当前访问的元素在集合中的位置信息。

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
class ConcreteIterator implements Iterator {
private int current = 0;
private ConcreteAggregate aggregate;

public ConcreteIterator(ConcreteAggregate aggregate) {
this.aggregate = aggregate;
}

@Override
public Object first() {
return aggregate.get(0);
}

@Override
public Object next() {
current++;
if (current < aggregate.size()) {
return aggregate.get(current);
}
return null;
}

@Override
public boolean isDone() {
return (current >= aggregate.size()) ? true : false;
}

@Override
public Object currentItem() {
return aggregate.get(current);
}
}

Aggregate : 定义创建 Iterator 对象的接口。

1
2
3
interface Aggregate {
public Iterator CreateIterator();
}

ConcreteAggregate : 实现 Iterator 接口,返回一个合适的 ConcreteIterator 实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ConcreteAggregate implements Aggregate {
private List<Object> items = new ArrayList<Object>();

@Override
public Iterator CreateIterator() {
return new ConcreteIterator(this);
}

public int size() {
return items.size();
}

public Object get(int index) {
return items.get(index);
}

public void set(int index, Object element) {
items.set(index, element);
}

public void add(Object element) {
items.add(element);
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class IteratorPattern {
public static void main(String[] args) {
ConcreteAggregate aggregate = new ConcreteAggregate();
aggregate.add("张三");
aggregate.add("李四");
aggregate.add("王五");
aggregate.add("赵六");

Iterator iter = new ConcreteIterator(aggregate);
Object item = iter.first();
System.out.println("第一个人是:" + item);
System.out.println("所有人的名单是:");
while (!iter.isDone()) {
System.out.println(iter.currentItem());
iter.next();
}
}
}

输出

1
2
3
4
5
6
第一个人是:张三
所有人的名单是:
张三
李四
王五
赵六

伪代码

在本例中, 迭代器模式用于遍历一个封装了访问微信好友关系功能的特殊集合。 该集合提供使用不同方式遍历档案资料的多个迭代器。

img

“好友 (friends)” 迭代器可用于遍历指定档案的好友。 “同事 (colleagues)” 迭代器也提供同样的功能, 但仅包括与目标用户在同一家公司工作的好友。 这两个迭代器都实现了同一个通用接口, 客户端能在不了解认证和发送 REST 请求等实现细节的情况下获取档案。

客户端仅通过接口与集合和迭代器交互, 也就不会同具体类耦合。 如果你决定将应用连接到全新的社交网络, 只需提供新的集合和迭代器类即可, 无需修改现有代码。

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
// 集合接口必须声明一个用于生成迭代器的工厂方法。如果程序中有不同类型的迭
// 代器,你也可以声明多个方法。
interface SocialNetwork is
method createFriendsIterator(profileId):ProfileIterator
method createCoworkersIterator(profileId):ProfileIterator


// 每个具体集合都与其返回的一组具体迭代器相耦合。但客户并不是这样的,因为
// 这些方法的签名将会返回迭代器接口。
class WeChat implements SocialNetwork is
// ...大量的集合代码应该放在这里...

// 迭代器创建代码。
method createFriendsIterator(profileId) is
return new WeChatIterator(this, profileId, "friends")
method createCoworkersIterator(profileId) is
return new WeChatIterator(this, profileId, "coworkers")


// 所有迭代器的通用接口。
interface ProfileIterator is
method getNext():Profile
method hasMore():bool


// 具体迭代器类。
class WeChatIterator implements ProfileIterator is
// 迭代器需要一个指向其遍历集合的引用。
private field weChat: WeChat
private field profileId, type: string

// 迭代器对象会独立于其他迭代器来对集合进行遍历。因此它必须保存迭代器
// 的状态。
private field currentPosition
private field cache: array of Profile

constructor WeChatIterator(weChat, profileId, type) is
this.weChat = weChat
this.profileId = profileId
this.type = type

private method lazyInit() is
if (cache == null)
cache = weChat.socialGraphRequest(profileId, type)

// 每个具体迭代器类都会自行实现通用迭代器接口。
method getNext() is
if (hasMore())
currentPosition++
return cache[currentPosition]

method hasMore() is
lazyInit()
return currentPosition < cache.length


// 这里还有一个有用的绝招:你可将迭代器传递给客户端类,无需让其拥有访问整
// 个集合的权限。这样一来,你就无需将集合暴露给客户端了。
//
// 还有另一个好处:你可在运行时将不同的迭代器传递给客户端,从而改变客户端
// 与集合互动的方式。这一方法可行的原因是客户端代码并没有和具体迭代器类相
// 耦合。
class SocialSpammer is
method send(iterator: ProfileIterator, message: string) is
while (iterator.hasMore())
profile = iterator.getNext()
System.sendEmail(profile.getEmail(), message)


// 应用程序(Application)类可对集合和迭代器进行配置,然后将其传递给客户
// 端代码。
class Application is
field network: SocialNetwork
field spammer: SocialSpammer

method config() is
if working with WeChat
this.network = new WeChat()
if working with LinkedIn
this.network = new LinkedIn()
this.spammer = new SocialSpammer()

method sendSpamToFriends(profile) is
iterator = network.createFriendsIterator(profile.getId())
spammer.send(iterator, "非常重要的消息")

method sendSpamToCoworkers(profile) is
iterator = network.createCoworkersIterator(profile.getId())
spammer.send(iterator, "非常重要的消息")

与其他模式的关系

案例

使用示例: 该模式在 Java 代码中很常见。 许多框架和程序库都会使用它来提供遍历其集合的标准方式。

下面是该模式在核心 Java 程序库中的一些示例:

识别方法: 迭代器可以通过导航方法 (例如 nextprevious等) 来轻松识别。 使用迭代器的客户端代码可能没有其所遍历的集合的直接访问权限。

参考资料

设计模式之命令模式

意图

命令模式(Command) 是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。

命令模式的交互

img

  • Client 创建一个 ConcreteCommand 对象并指定他的 Receiver 对象。
  • 某个 Invoker 对象存储该 ConcreteCommand 对象。
  • 该 Invoker 通过调用 Command 对象的 Execute 操作来提交一个请求。若该命令是可撤销的,ConcreteCommand 就在执行 Execute 操作之前存储当前状态以用于取消该命令。
  • ConcreteCommand 对象对调用它的 Receiver 的一些操作以执行该请求。

命令模式的要点

  • 命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
  • 每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
  • 命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
  • 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
  • 命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。

适用场景

  • 如果你需要通过操作来参数化对象, 可使用命令模式。
  • 如果你想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式。
  • 如果你想要实现操作回滚功能, 可使用命令模式。

结构

img

结构说明

  1. 发送者 (Sender)——亦称 “触发者 (Invoker)”——类负责对请求进行初始化, 其中必须包含一个成员变量来存储对于命令对象的引用。 发送者触发命令, 而不向接收者直接发送请求。 注意, 发送者并不负责创建命令对象: 它通常会通过构造函数从客户端处获得预先生成的命令。

  2. 命令 (Command) 接口通常仅声明一个执行命令的方法。

  3. 具体命令 (Concrete Commands) 会实现各种类型的请求。 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象。 但为了简化代码, 这些类可以进行合并。

    接收对象执行方法所需的参数可以声明为具体命令的成员变量。 你可以将命令对象设为不可变, 仅允许通过构造函数对这些成员变量进行初始化。

  4. 接收者 (Receiver) 类包含部分业务逻辑。 几乎任何对象都可以作为接收者。 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作。

  5. 客户端 (Client) 会创建并配置具体命令对象。 客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。 此后, 生成的命令就可以与一个或多个发送者相关联了。

结构代码范式

Command : 用来声明执行操作的接口。

1
2
3
4
5
6
7
8
abstract class Command {
protected Receiver receiver;
public Command(Receiver receiver) {
this.receiver = receiver;
}

public abstract void Execute();
}

ConcreteCommand : 将一个接收者对象绑定一个动作,调用接收者相应的操作,以实现 Execute。

1
2
3
4
5
6
7
8
9
10
class ConcreteCommand extends Command {
public ConcreteCommand(Receiver receiver) {
super(receiver);
}

@Override
public void Execute() {
receiver.Action();
}
}

Invoker : 要求该命令执行这个请求。

1
2
3
4
5
6
7
8
9
10
11
class Invoker {
private Command command;

public Invoker(Command command) {
this.command = command;
}

public void ExecuteCommand() {
command.Execute();
}
}

Receiver : 知道如何实施与执行一个与请求相关的操作,任何类都可能作为一个接收者。

1
2
3
4
5
class Receiver {
public void Action() {
System.out.println("执行请求");
}
}

Client : 创建一个具体命令对象并设定它的接受者。

1
2
3
4
5
6
7
8
public class CommandPattern {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command cmd = new ConcreteCommand(receiver);
Invoker invoker = new Invoker(cmd);
invoker.ExecuteCommand();
}
}

伪代码

在本例中, 命令模式会记录已执行操作的历史记录, 以在需要时撤销操作。

img

有些命令会改变编辑器的状态 (例如剪切和粘贴), 它们可在执行相关操作前对编辑器的状态进行备份。 命令执行后会和当前点备份的编辑器状态一起被放入命令历史 (命令对象栈)。 此后, 如果用户需要进行回滚操作, 程序可从历史记录中取出最近的命令, 读取相应的编辑器状态备份, 然后进行恢复。

客户端代码 (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
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
// 命令基类会为所有具体命令定义通用接口。
abstract class Command is
protected field app: Application
protected field editor: Editor
protected field backup: text

constructor Command(app: Application, editor: Editor) is
this.app = app
this.editor = editor

// 备份编辑器状态。
method saveBackup() is
backup = editor.text

// 恢复编辑器状态。
method undo() is
editor.text = backup

// 执行方法被声明为抽象以强制所有具体命令提供自己的实现。该方法必须根
// 据命令是否更改编辑器的状态返回 true 或 false。
abstract method execute()


// 这里是具体命令。
class CopyCommand extends Command is
// 复制命令不会被保存到历史记录中,因为它没有改变编辑器的状态。
method execute() is
app.clipboard = editor.getSelection()
return false

class CutCommand extends Command is
// 剪切命令改变了编辑器的状态,因此它必须被保存到历史记录中。只要方法
// 返回 true,它就会被保存。
method execute() is
saveBackup()
app.clipboard = editor.getSelection()
editor.deleteSelection()
return true

class PasteCommand extends Command is
method execute() is
saveBackup()
editor.replaceSelection(app.clipboard)
return true

// 撤销操作也是一个命令。
class UndoCommand extends Command is
method execute() is
app.undo()
return false


// 全局命令历史记录就是一个堆桟。
class CommandHistory is
private field history: array of Command

// 后进...
method push(c: Command) is
// 将命令压入历史记录数组的末尾。

// ...先出
method pop():Command is
// 从历史记录中取出最近的命令。


// 编辑器类包含实际的文本编辑操作。它会担任接收者的角色:最后所有命令都会
// 将执行工作委派给编辑器的方法。
class Editor is
field text: string

method getSelection() is
// 返回选中的文字。

method deleteSelection() is
// 删除选中的文字。

method replaceSelection(text) is
// 在当前位置插入剪贴板中的内容。

// 应用程序类会设置对象之间的关系。它会担任发送者的角色:当需要完成某些工
// 作时,它会创建并执行一个命令对象。
class Application is
field clipboard: string
field editors: array of Editors
field activeEditor: Editor
field history: CommandHistory

// 将命令分派给 UI 对象的代码可能会是这样的。
method createUI() is
// ...
copy = function() { executeCommand(
new CopyCommand(this, activeEditor)) }
copyButton.setCommand(copy)
shortcuts.onKeyPress("Ctrl+C", copy)

cut = function() { executeCommand(
new CutCommand(this, activeEditor)) }
cutButton.setCommand(cut)
shortcuts.onKeyPress("Ctrl+X", cut)

paste = function() { executeCommand(
new PasteCommand(this, activeEditor)) }
pasteButton.setCommand(paste)
shortcuts.onKeyPress("Ctrl+V", paste)

undo = function() { executeCommand(
new UndoCommand(this, activeEditor)) }
undoButton.setCommand(undo)
shortcuts.onKeyPress("Ctrl+Z", undo)

// 执行一个命令并检查它是否需要被添加到历史记录中。
method executeCommand(command) is
if (command.execute)
history.push(command)

// 从历史记录中取出最近的命令并运行其 undo(撤销)方法。请注意,你并
// 不知晓该命令所属的类。但是我们不需要知晓,因为命令自己知道如何撤销
// 其动作。
method undo() is
command = history.pop()
if (command != null)
command.undo()

与其他模式的关系

  • 责任链模式命令模式中介者模式观察者模式用于处理请求发送者和接收者之间的不同连接方式:
    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 责任链的管理者可使用命令模式实现。 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作。
    还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。
  • 你可以同时使用命令备忘录模式来实现 “撤销”。 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。
  • 命令策略模式看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。
    • 你可以使用命令来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 你可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。
    • 另一方面, 策略通常可用于描述完成某件事的不同方式, 让你能够在同一个上下文类中切换算法。
  • 原型模式可用于保存命令的历史记录。
  • 你可以将访问者模式视为命令模式的加强版本, 其对象可对不同类的多种对象执行操作。

案例

使用示例:命令模式在 Java 代码中很常见。 大部分情况下, 它被用于代替包含行为的参数化 UI 元素的回调函数, 此外还被用于对任务进行排序和记录操作历史记录等。

以下是在核心 Java 程序库中的一些示例:

识别方法: 命令模式可以通过抽象或接口类型 (发送者) 中的行为方法来识别, 该类型调用另一个不同的抽象或接口类型 (接收者) 实现中的方法, 该实现则是在创建时由命令模式的实现封装。 命令类通常仅限于一些特殊行为。

参考资料