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

行为模式-访问者模式

亦称: Visitor

意图

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

问题

假如你的团队开发了一款能够使用巨型图像中地理信息的应用程序。 图像中的每个节点既能代表复杂实体 (例如一座城市), 也能代表更精细的对象 (例如工业区和旅游景点等)。 如果节点代表的真实对象之间存在公路, 那么这些节点就会相互连接。 在程序内部, 每个节点的类型都由其所属的类来表示, 每个特定的节点则是一个对象。

将图像导出为 XML。

一段时间后, 你接到了实现将图像导出到 XML 文件中的任务。 这些工作最初看上去非常简单。 你计划为每个节点类添加导出函数, 然后递归执行图像中每个节点的导出函数。 解决方案简单且优雅: 使用多态机制可以让导出方法的调用代码不会和具体的节点类相耦合。

但你不太走运, 系统架构师拒绝批准对已有节点类进行修改。 他认为这些代码已经是产品了, 不想冒险对其进行修改, 因为修改可能会引入潜在的缺陷。

所有节点的类中都必须添加导出至 XML 文件的方法, 但如果在修改代码的过程中引入了任何缺陷, 那么整个程序都会面临风险。

此外, 他还质疑在节点类中包含导出 XML 文件的代码是否有意义。 这些类的主要工作是处理地理数据。 导出 XML 文件的代码放在这里并不合适。

还有另一个原因, 那就是在此项任务完成后, 营销部门很有可能会要求程序提供导出其他类型文件的功能, 或者提出其他奇怪的要求。 这样你很可能会被迫再次修改这些重要但脆弱的类。

解决方案

访问者模式建议将新行为放入一个名为访问者的独立类中, 而不是试图将其整合到已有类中。 现在, 需要执行操作的原始对象将作为参数被传递给访问者中的方法, 让方法能访问对象所包含的一切必要数据。

如果现在该操作能在不同类的对象上执行会怎么样呢? 比如在我们的示例中, 各节点类导出 XML 文件的实际实现很可能会稍有不同。 因此, 访问者类可以定义一组 (而不是一个) 方法, 且每个方法可接收不同类型的参数, 如下所示:

1
2
3
4
5
class ExportVisitor implements Visitor is
method doForCity(City c) { ... }
method doForIndustry(Industry f) { ... }
method doForSightSeeing(SightSeeing ss) { ... }
// ...

但我们究竟应该如何调用这些方法 (尤其是在处理整个图像方面) 呢? 这些方法的签名各不相同, 因此我们不能使用多态机制。 为了可以挑选出能够处理特定对象的访问者方法, 我们需要对它的类进行检查。 这是不是听上去像个噩梦呢?

1
2
3
4
5
6
7
foreach (Node node in graph)
if (node instanceof City)
exportVisitor.doForCity((City) node)
if (node instanceof Industry)
exportVisitor.doForIndustry((Industry) node)
// ...
}

你可能会问, 我们为什么不使用方法重载呢? 就是使用相同的方法名称, 但它们的参数不同。 不幸的是, 即使我们的编程语言 (例如 Java 和 C#) 支持重载也不行。 由于我们无法提前知晓节点对象所属的类, 所以重载机制无法执行正确的方法。 方法会将 节点基类作为输入参数的默认类型。

但是, 访问者模式可以解决这个问题。 它使用了一种名为双分派的技巧, 不使用累赘的条件语句也可下执行正确的方法。 与其让客户端来选择调用正确版本的方法, 不如将选择权委派给作为参数传递给访问者的对象。 由于该对象知晓其自身的类, 因此能更自然地在访问者中选出正确的方法。 它们会 “接收” 一个访问者并告诉其应执行的访问者方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 客户端代码
foreach (Node node in graph)
node.accept(exportVisitor)

// 城市
class City is
method accept(Visitor v) is
v.doForCity(this)
// ...

// 工业区
class Industry is
method accept(Visitor v) is
v.doForIndustry(this)
// ...

我承认最终还是修改了节点类, 但毕竟改动很小, 且使得我们能够在后续进一步添加行为时无需再次修改代码。

现在, 如果我们抽取出所有访问者的通用接口, 所有已有的节点都能与我们在程序中引入的任何访问者交互。 如果需要引入与节点相关的某个行为, 你只需要实现一个新的访问者类即可。

真实世界类比

优秀的保险代理人总能为不同类型的团体提供不同的保单。

假如有这样一位非常希望赢得新客户的资深保险代理人。 他可以拜访街区中的每栋楼, 尝试向每个路人推销保险。 所以, 根据大楼内组织类型的不同, 他可以提供专门的保单:

  • 如果建筑是居民楼, 他会推销医疗保险。
  • 如果建筑是银行, 他会推销失窃保险。
  • 如果建筑是咖啡厅, 他会推销火灾和洪水保险。

访问者模式结构

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

伪代码

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

通过访问者对象将各种类型的对象导出为 XML 格式文件。

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)

如果你并不十分理解为何本例中需要使用 accept接收方法, 我的一篇文章访问者和双分派详细解释了这个问题。

访问者模式适合应用场景

如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。

访问者模式通过在访问者对象中为多个目标类提供相同操作的变体, 让你能在属于不同类的一组对象上执行同一操作。

可使用访问者模式来清理辅助行为的业务逻辑。

该模式会将所有非主要的行为抽取到一组访问者类中, 使得程序的主要类能更专注于主要的工作。

当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。

你可将该行为抽取到单独的访问者类中, 只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。

实现方式

  1. 在访问者接口中声明一组 “访问” 方法, 分别对应程序中的每个具体元素类。

  2. 声明元素接口。 如果程序中已有元素类层次接口, 可在层次结构基类中添加抽象的 “接收” 方法。 该方法必须接受访问者对象作为参数。

  3. 在所有具体元素类中实现接收方法。 这些方法必须将调用重定向到当前元素对应的访问者对象中的访问者方法上。

  4. 元素类只能通过访问者接口与访问者进行交互。 不过访问者必须知晓所有的具体元素类, 因为这些类在访问者方法中都被作为参数类型引用。

  5. 为每个无法在元素层次结构中实现的行为创建一个具体访问者类并实现所有的访问者方法。

    你可能会遇到访问者需要访问元素类的部分私有成员变量的情况。 在这种情况下, 你要么将这些变量或方法设为公有, 这将破坏元素的封装; 要么将访问者类嵌入到元素类中。 后一种方式只有在支持嵌套类的编程语言中才可能实现。

  6. 客户端必须创建访问者对象并通过 “接收” 方法将其传递给元素。

访问者模式优缺点

优点

  • 开闭原则。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。
  • 单一职责原则。 可将同一行为的不同版本移到同一个类中。
  • 访问者对象可以在与各种对象交互时收集一些有用的信息。 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。

缺点

  • 部分客户端可能会受到算法框架的限制。
  • 通过子类抑制默认步骤实现可能会导致违反_里氏替换原则_。
  • 模板方法中的步骤越多, 其维护工作就可能会越困难。

与其他模式的关系

代码示例

访问者是一种行为设计模式, 允许你在不修改已有代码的情况下向已有类层次结构中增加新的行为。

在 C# 中使用模式

复杂度: ★★★

流行度: ★☆☆

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

概念示例

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

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

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
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
using System;
using System.Collections.Generic;

namespace RefactoringGuru.DesignPatterns.Visitor.Conceptual
{
// The Component interface declares an `accept` method that should take the
// base visitor interface as an argument.
public interface IComponent
{
void Accept(IVisitor visitor);
}

// Each Concrete Component must implement the `Accept` method in such a way
// that it calls the visitor's method corresponding to the component's
// class.
public class ConcreteComponentA : IComponent
{
// Note that we're calling `VisitConcreteComponentA`, which matches the
// current class name. This way we let the visitor know the class of the
// component it works with.
public void Accept(IVisitor visitor)
{
visitor.VisitConcreteComponentA(this);
}

// Concrete Components may have special methods that don't exist in
// their base class or interface. The Visitor is still able to use these
// methods since it's aware of the component's concrete class.
public string ExclusiveMethodOfConcreteComponentA()
{
return "A";
}
}

public class ConcreteComponentB : IComponent
{
// Same here: VisitConcreteComponentB => ConcreteComponentB
public void Accept(IVisitor visitor)
{
visitor.VisitConcreteComponentB(this);
}

public string SpecialMethodOfConcreteComponentB()
{
return "B";
}
}

// The Visitor Interface declares a set of visiting methods that correspond
// to component classes. The signature of a visiting method allows the
// visitor to identify the exact class of the component that it's dealing
// with.
public interface IVisitor
{
void VisitConcreteComponentA(ConcreteComponentA element);

void VisitConcreteComponentB(ConcreteComponentB element);
}

// Concrete Visitors implement several versions of the same algorithm, which
// can work with all concrete component classes.
//
// You can experience the biggest benefit of the Visitor pattern when using
// it with a complex object structure, such as a Composite tree. In this
// case, it might be helpful to store some intermediate state of the
// algorithm while executing visitor's methods over various objects of the
// structure.
class ConcreteVisitor1 : IVisitor
{
public void VisitConcreteComponentA(ConcreteComponentA element)
{
Console.WriteLine(element.ExclusiveMethodOfConcreteComponentA() + " + ConcreteVisitor1");
}

public void VisitConcreteComponentB(ConcreteComponentB element)
{
Console.WriteLine(element.SpecialMethodOfConcreteComponentB() + " + ConcreteVisitor1");
}
}

class ConcreteVisitor2 : IVisitor
{
public void VisitConcreteComponentA(ConcreteComponentA element)
{
Console.WriteLine(element.ExclusiveMethodOfConcreteComponentA() + " + ConcreteVisitor2");
}

public void VisitConcreteComponentB(ConcreteComponentB element)
{
Console.WriteLine(element.SpecialMethodOfConcreteComponentB() + " + ConcreteVisitor2");
}
}

public class Client
{
// The client code can run visitor operations over any set of elements
// without figuring out their concrete classes. The accept operation
// directs a call to the appropriate operation in the visitor object.
public static void ClientCode(List<IComponent> components, IVisitor visitor)
{
foreach (var component in components)
{
component.Accept(visitor);
}
}
}

class Program
{
static void Main(string[] args)
{
List<IComponent> components = new List<IComponent>
{
new ConcreteComponentA(),
new ConcreteComponentB()
};

Console.WriteLine("The client code works with all visitors via the base Visitor interface:");
var visitor1 = new ConcreteVisitor1();
Client.ClientCode(components,visitor1);

Console.WriteLine();

Console.WriteLine("It allows the same client code to work with different types of visitors:");
var visitor2 = new ConcreteVisitor2();
Client.ClientCode(components, visitor2);
}
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1

It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2

在 C++ 中使用模式

复杂度: ★★★

流行度: ★☆☆

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

概念示例

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

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

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
120
121
122
/**
* The Visitor Interface declares a set of visiting methods that correspond to
* component classes. The signature of a visiting method allows the visitor to
* identify the exact class of the component that it's dealing with.
*/
class ConcreteComponentA;
class ConcreteComponentB;

class Visitor {
public:
virtual void VisitConcreteComponentA(const ConcreteComponentA *element) const = 0;
virtual void VisitConcreteComponentB(const ConcreteComponentB *element) const = 0;
};

/**
* The Component interface declares an `accept` method that should take the base
* visitor interface as an argument.
*/

class Component {
public:
virtual ~Component() {}
virtual void Accept(Visitor *visitor) const = 0;
};

/**
* Each Concrete Component must implement the `Accept` method in such a way that
* it calls the visitor's method corresponding to the component's class.
*/
class ConcreteComponentA : public Component {
/**
* Note that we're calling `visitConcreteComponentA`, which matches the
* current class name. This way we let the visitor know the class of the
* component it works with.
*/
public:
void Accept(Visitor *visitor) const override {
visitor->VisitConcreteComponentA(this);
}
/**
* Concrete Components may have special methods that don't exist in their base
* class or interface. The Visitor is still able to use these methods since
* it's aware of the component's concrete class.
*/
std::string ExclusiveMethodOfConcreteComponentA() const {
return "A";
}
};

class ConcreteComponentB : public Component {
/**
* Same here: visitConcreteComponentB => ConcreteComponentB
*/
public:
void Accept(Visitor *visitor) const override {
visitor->VisitConcreteComponentB(this);
}
std::string SpecialMethodOfConcreteComponentB() const {
return "B";
}
};

/**
* Concrete Visitors implement several versions of the same algorithm, which can
* work with all concrete component classes.
*
* You can experience the biggest benefit of the Visitor pattern when using it
* with a complex object structure, such as a Composite tree. In this case, it
* might be helpful to store some intermediate state of the algorithm while
* executing visitor's methods over various objects of the structure.
*/
class ConcreteVisitor1 : public Visitor {
public:
void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor1\n";
}

void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor1\n";
}
};

class ConcreteVisitor2 : public Visitor {
public:
void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor2\n";
}
void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor2\n";
}
};
/**
* The client code can run visitor operations over any set of elements without
* figuring out their concrete classes. The accept operation directs a call to
* the appropriate operation in the visitor object.
*/
void ClientCode(std::array<const Component *, 2> components, Visitor *visitor) {
// ...
for (const Component *comp : components) {
comp->Accept(visitor);
}
// ...
}

int main() {
std::array<const Component *, 2> components = {new ConcreteComponentA, new ConcreteComponentB};
std::cout << "The client code works with all visitors via the base Visitor interface:\n";
ConcreteVisitor1 *visitor1 = new ConcreteVisitor1;
ClientCode(components, visitor1);
std::cout << "\n";
std::cout << "It allows the same client code to work with different types of visitors:\n";
ConcreteVisitor2 *visitor2 = new ConcreteVisitor2;
ClientCode(components, visitor2);

for (const Component *comp : components) {
delete comp;
}
delete visitor1;
delete visitor2;

return 0;
}

Output.txt: 执行结果

1
2
3
4
5
6
7
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1

It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2

在 Java 中使用模式

复杂度: ★★★

流行度: ★☆☆

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

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

将形状导出为 XML 文件

在本例中, 我们希望将一系列几何形状导出为 XML 文件。 重点在于我们不希望直接修改形状代码, 或者至少能确保最小程度的修改。

最终, 访问者模式建立了一个框架, 允许我们在不修改已有类的情况下向形状层次结构中添加新的行为。

shapes

shapes/Shape.java: 通用形状接口

1
2
3
4
5
6
7
8
9
package refactoring_guru.visitor.example.shapes;

import refactoring_guru.visitor.example.visitor.Visitor;

public interface Shape {
void move(int x, int y);
void draw();
String accept(Visitor visitor);
}

shapes/Dot.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
package refactoring_guru.visitor.example.shapes;

import refactoring_guru.visitor.example.visitor.Visitor;

public class Dot implements Shape {
private int id;
private int x;
private int y;

public Dot() {
}

public Dot(int id, int x, int y) {
this.id = id;
this.x = x;
this.y = y;
}

@Override
public void move(int x, int y) {
// move shape
}

@Override
public void draw() {
// draw shape
}

@Override
public String accept(Visitor visitor) {
return visitor.visitDot(this);
}

public int getX() {
return x;
}

public int getY() {
return y;
}

public int getId() {
return id;
}
}

shapes/Circle.java: 圆形

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

import refactoring_guru.visitor.example.visitor.Visitor;

public class Circle extends Dot {
private int radius;

public Circle(int id, int x, int y, int radius) {
super(id, x, y);
this.radius = radius;
}

@Override
public String accept(Visitor visitor) {
return visitor.visitCircle(this);
}

public int getRadius() {
return radius;
}
}

shapes/Rectangle.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
package refactoring_guru.visitor.example.shapes;

import refactoring_guru.visitor.example.visitor.Visitor;

public class Rectangle implements Shape {
private int id;
private int x;
private int y;
private int width;
private int height;

public Rectangle(int id, int x, int y, int width, int height) {
this.id = id;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}

@Override
public String accept(Visitor visitor) {
return visitor.visitRectangle(this);
}

@Override
public void move(int x, int y) {
// move shape
}

@Override
public void draw() {
// draw shape
}

public int getId() {
return id;
}

public int getX() {
return x;
}

public int getY() {
return y;
}

public int getWidth() {
return width;
}

public int getHeight() {
return height;
}
}

shapes/CompoundShape.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
package refactoring_guru.visitor.example.shapes;

import refactoring_guru.visitor.example.visitor.Visitor;

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

public class CompoundShape implements Shape {
public int id;
public List<Shape> children = new ArrayList<>();

public CompoundShape(int id) {
this.id = id;
}

@Override
public void move(int x, int y) {
// move shape
}

@Override
public void draw() {
// draw shape
}

public int getId() {
return id;
}

@Override
public String accept(Visitor visitor) {
return visitor.visitCompoundGraphic(this);
}

public void add(Shape shape) {
children.add(shape);
}
}

visitor

visitor/Visitor.java: 通用访问者接口

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

import refactoring_guru.visitor.example.shapes.Circle;
import refactoring_guru.visitor.example.shapes.CompoundShape;
import refactoring_guru.visitor.example.shapes.Dot;
import refactoring_guru.visitor.example.shapes.Rectangle;

public interface Visitor {
String visitDot(Dot dot);

String visitCircle(Circle circle);

String visitRectangle(Rectangle rectangle);

String visitCompoundGraphic(CompoundShape cg);
}

visitor/XMLExportVisitor.java: 具体访问者, 将所有形状导出为 XML 文件

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

import refactoring_guru.visitor.example.shapes.*;

public class XMLExportVisitor implements Visitor {

public String export(Shape... args) {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "\n");
for (Shape shape : args) {
sb.append(shape.accept(this)).append("\n");
}
return sb.toString();
}

public String visitDot(Dot d) {
return "<dot>" + "\n" +
" <id>" + d.getId() + "</id>" + "\n" +
" <x>" + d.getX() + "</x>" + "\n" +
" <y>" + d.getY() + "</y>" + "\n" +
"</dot>";
}

public String visitCircle(Circle c) {
return "<circle>" + "\n" +
" <id>" + c.getId() + "</id>" + "\n" +
" <x>" + c.getX() + "</x>" + "\n" +
" <y>" + c.getY() + "</y>" + "\n" +
" <radius>" + c.getRadius() + "</radius>" + "\n" +
"</circle>";
}

public String visitRectangle(Rectangle r) {
return "<rectangle>" + "\n" +
" <id>" + r.getId() + "</id>" + "\n" +
" <x>" + r.getX() + "</x>" + "\n" +
" <y>" + r.getY() + "</y>" + "\n" +
" <width>" + r.getWidth() + "</width>" + "\n" +
" <height>" + r.getHeight() + "</height>" + "\n" +
"</rectangle>";
}

public String visitCompoundGraphic(CompoundShape cg) {
return "<compound_graphic>" + "\n" +
" <id>" + cg.getId() + "</id>" + "\n" +
_visitCompoundGraphic(cg) +
"</compound_graphic>";
}

private String _visitCompoundGraphic(CompoundShape cg) {
StringBuilder sb = new StringBuilder();
for (Shape shape : cg.children) {
String obj = shape.accept(this);
// Proper indentation for sub-objects.
obj = " " + obj.replace("\n", "\n ") + "\n";
sb.append(obj);
}
return sb.toString();
}

}

Demo.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
package refactoring_guru.visitor.example;

import refactoring_guru.visitor.example.shapes.*;
import refactoring_guru.visitor.example.visitor.XMLExportVisitor;

public class Demo {
public static void main(String[] args) {
Dot dot = new Dot(1, 10, 55);
Circle circle = new Circle(2, 23, 15, 10);
Rectangle rectangle = new Rectangle(3, 10, 17, 20, 30);

CompoundShape compoundShape = new CompoundShape(4);
compoundShape.add(dot);
compoundShape.add(circle);
compoundShape.add(rectangle);

CompoundShape c = new CompoundShape(5);
c.add(dot);
compoundShape.add(c);

export(circle, compoundShape);
}

private static void export(Shape... shapes) {
XMLExportVisitor exportVisitor = new XMLExportVisitor();
System.out.println(exportVisitor.export(shapes));
}
}

OutputDemo.png: 执行结果

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
<?xml version="1.0" encoding="utf-8"?>
<circle>
<id>2</id>
<x>23</x>
<y>15</y>
<radius>10</radius>
</circle>

<?xml version="1.0" encoding="utf-8"?>
<compound_graphic>
<id>4</id>
<dot>
<id>1</id>
<x>10</x>
<y>55</y>
</dot>
<circle>
<id>2</id>
<x>23</x>
<y>15</y>
<radius>10</radius>
</circle>
<rectangle>
<id>3</id>
<x>10</x>
<y>17</y>
<width>20</width>
<height>30</height>
</rectangle>
<compound_graphic>
<id>5</id>
<dot>
<id>1</id>
<x>10</x>
<y>55</y>
</dot>
</compound_graphic>
</compound_graphic>

在 PHP 中使用模式

复杂度: ★★★

流行度: ★☆☆

使用示例: 访问者模式在 PHP 代码中不太常用, 因为它不仅复杂, 应用范围也比较狭窄。

概念示例

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

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

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<?php

namespace RefactoringGuru\Visitor\Conceptual;

/**
* The Component interface declares an `accept` method that should take the base
* visitor interface as an argument.
*/
interface Component
{
public function accept(Visitor $visitor): void;
}

/**
* Each Concrete Component must implement the `accept` method in such a way that
* it calls the visitor's method corresponding to the component's class.
*/
class ConcreteComponentA implements Component
{
/**
* Note that we're calling `visitConcreteComponentA`, which matches the
* current class name. This way we let the visitor know the class of the
* component it works with.
*/
public function accept(Visitor $visitor): void
{
$visitor->visitConcreteComponentA($this);
}

/**
* Concrete Components may have special methods that don't exist in their
* base class or interface. The Visitor is still able to use these methods
* since it's aware of the component's concrete class.
*/
public function exclusiveMethodOfConcreteComponentA(): string
{
return "A";
}
}

class ConcreteComponentB implements Component
{
/**
* Same here: visitConcreteComponentB => ConcreteComponentB
*/
public function accept(Visitor $visitor): void
{
$visitor->visitConcreteComponentB($this);
}

public function specialMethodOfConcreteComponentB(): string
{
return "B";
}
}

/**
* The Visitor Interface declares a set of visiting methods that correspond to
* component classes. The signature of a visiting method allows the visitor to
* identify the exact class of the component that it's dealing with.
*/
interface Visitor
{
public function visitConcreteComponentA(ConcreteComponentA $element): void;

public function visitConcreteComponentB(ConcreteComponentB $element): void;
}

/**
* Concrete Visitors implement several versions of the same algorithm, which can
* work with all concrete component classes.
*
* You can experience the biggest benefit of the Visitor pattern when using it
* with a complex object structure, such as a Composite tree. In this case, it
* might be helpful to store some intermediate state of the algorithm while
* executing visitor's methods over various objects of the structure.
*/
class ConcreteVisitor1 implements Visitor
{
public function visitConcreteComponentA(ConcreteComponentA $element): void
{
echo $element->exclusiveMethodOfConcreteComponentA() . " + ConcreteVisitor1\n";
}

public function visitConcreteComponentB(ConcreteComponentB $element): void
{
echo $element->specialMethodOfConcreteComponentB() . " + ConcreteVisitor1\n";
}
}

class ConcreteVisitor2 implements Visitor
{
public function visitConcreteComponentA(ConcreteComponentA $element): void
{
echo $element->exclusiveMethodOfConcreteComponentA() . " + ConcreteVisitor2\n";
}

public function visitConcreteComponentB(ConcreteComponentB $element): void
{
echo $element->specialMethodOfConcreteComponentB() . " + ConcreteVisitor2\n";
}
}

/**
* The client code can run visitor operations over any set of elements without
* figuring out their concrete classes. The accept operation directs a call to
* the appropriate operation in the visitor object.
*/
function clientCode(array $components, Visitor $visitor)
{
// ...
foreach ($components as $component) {
$component->accept($visitor);
}
// ...
}

$components = [
new ConcreteComponentA(),
new ConcreteComponentB(),
];

echo "The client code works with all visitors via the base Visitor interface:\n";
$visitor1 = new ConcreteVisitor1();
clientCode($components, $visitor1);
echo "\n";

echo "It allows the same client code to work with different types of visitors:\n";
$visitor2 = new ConcreteVisitor2();
clientCode($components, $visitor2);

Output.txt: 执行结果

1
2
3
4
5
6
7
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1

It allows the same client code to work with different types of visitors:
A + ConcreteVisitor1
B + ConcreteVisitor2

真实世界示例

在本例中, 访问者模式在已有类层次结构 ( “公司 > 部门 > 雇员”) 中添加了报表功能

在将访问者构架添加到程序中后, 你就能够在无需修改已有类的前提下将其他类似行为轻松添加到程序中了。

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
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
<?php

namespace RefactoringGuru\Visitor\RealWorld;

/**
* The Component interface declares a method of accepting visitor objects.
*
* In this method, a Concrete Component must call a specific Visitor's method
* that has the same parameter type as that component.
*/
interface Entity
{
public function accept(Visitor $visitor): string;
}

/**
* The Company Concrete Component.
*/
class Company implements Entity
{
private $name;

/**
* @var Department[]
*/
private $departments;

public function __construct(string $name, array $departments)
{
$this->name = $name;
$this->departments = $departments;
}

public function getName(): string
{
return $this->name;
}

public function getDepartments(): array
{
return $this->departments;
}

// ...

public function accept(Visitor $visitor): string
{
// See, the Company component must call the visitCompany method. The
// same principle applies to all components.
return $visitor->visitCompany($this);
}
}

/**
* The Department Concrete Component.
*/
class Department implements Entity
{
private $name;

/**
* @var Employee[]
*/
private $employees;

public function __construct(string $name, array $employees)
{
$this->name = $name;
$this->employees = $employees;
}

public function getName(): string
{
return $this->name;
}

public function getEmployees(): array
{
return $this->employees;
}

public function getCost(): int
{
$cost = 0;
foreach ($this->employees as $employee) {
$cost += $employee->getSalary();
}

return $cost;
}

// ...

public function accept(Visitor $visitor): string
{
return $visitor->visitDepartment($this);
}
}

/**
* The Employee Concrete Component.
*/
class Employee implements Entity
{
private $name;

private $position;

private $salary;

public function __construct(string $name, string $position, int $salary)
{
$this->name = $name;
$this->position = $position;
$this->salary = $salary;
}

public function getName(): string
{
return $this->name;
}

public function getPosition(): string
{
return $this->position;
}

public function getSalary(): int
{
return $this->salary;
}

// ...

public function accept(Visitor $visitor): string
{
return $visitor->visitEmployee($this);
}
}

/**
* The Visitor interface declares a set of visiting methods for each of the
* Concrete Component classes.
*/
interface Visitor
{
public function visitCompany(Company $company): string;

public function visitDepartment(Department $department): string;

public function visitEmployee(Employee $employee): string;
}

/**
* The Concrete Visitor must provide implementations for every single class of
* the Concrete Components.
*/
class SalaryReport implements Visitor
{
public function visitCompany(Company $company): string
{
$output = "";
$total = 0;

foreach ($company->getDepartments() as $department) {
$total += $department->getCost();
$output .= "\n--" . $this->visitDepartment($department);
}

$output = $company->getName() .
" (" . money_format("%i", $total) . ")\n" . $output;

return $output;
}

public function visitDepartment(Department $department): string
{
$output = "";

foreach ($department->getEmployees() as $employee) {
$output .= " " . $this->visitEmployee($employee);
}

$output = $department->getName() .
" (" . money_format("%i", $department->getCost()) . ")\n\n" .
$output;

return $output;
}

public function visitEmployee(Employee $employee): string
{
return money_format("%#6n", $employee->getSalary()) .
" " . $employee->getName() .
" (" . $employee->getPosition() . ")\n";
}
}

/**
* The client code.
*/

$mobileDev = new Department("Mobile Development", [
new Employee("Albert Falmore", "designer", 100000),
new Employee("Ali Halabay", "programmer", 100000),
new Employee("Sarah Konor", "programmer", 90000),
new Employee("Monica Ronaldino", "QA engineer", 31000),
new Employee("James Smith", "QA engineer", 30000),
]);
$techSupport = new Department("Tech Support", [
new Employee("Larry Ulbrecht", "supervisor", 70000),
new Employee("Elton Pale", "operator", 30000),
new Employee("Rajeet Kumar", "operator", 30000),
new Employee("John Burnovsky", "operator", 34000),
new Employee("Sergey Korolev", "operator", 35000),
]);
$company = new Company("SuperStarDevelopment", [$mobileDev, $techSupport]);

setlocale(LC_MONETARY, 'en_US');
$report = new SalaryReport();

echo "Client: I can print a report for a whole company:\n\n";
echo $company->accept($report);

echo "\nClient: ...or just for a single department:\n\n";
echo $techSupport->accept($report);

// $export = new JSONExport();
// echo $company->accept($export);

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 can print a report for a whole company:

SuperStarDevelopment (USD550,000.00)

--Mobile Development (USD351,000.00)

$100,000.00 Albert Falmore (designer)
$100,000.00 Ali Halabay (programmer)
$ 90,000.00 Sarah Konor (programmer)
$ 31,000.00 Monica Ronaldino (QA engineer)
$ 30,000.00 James Smith (QA engineer)

--Tech Support (USD199,000.00)

$ 70,000.00 Larry Ulbrecht (supervisor)
$ 30,000.00 Elton Pale (operator)
$ 30,000.00 Rajeet Kumar (operator)
$ 34,000.00 John Burnovsky (operator)
$ 35,000.00 Sergey Korolev (operator)

Client: ...or just for a single department:

Tech Support (USD199,000.00)

$ 70,000.00 Larry Ulbrecht (supervisor)
$ 30,000.00 Elton Pale (operator)
$ 30,000.00 Rajeet Kumar (operator)
$ 34,000.00 John Burnovsky (operator)
$ 35,000.00 Sergey Korolev (operator)

在 Python 中使用模式

复杂度: ★★★

流行度: ★☆☆

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

概念示例

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

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

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List


class Component(ABC):
"""
The Component interface declares an `accept` method that should take the
base visitor interface as an argument.
"""

@abstractmethod
def accept(self, visitor: Visitor) -> None:
pass


class ConcreteComponentA(Component):
"""
Each Concrete Component must implement the `accept` method in such a way
that it calls the visitor's method corresponding to the component's class.
"""

def accept(self, visitor: Visitor) -> None:
"""
Note that we're calling `visitConcreteComponentA`, which matches the
current class name. This way we let the visitor know the class of the
component it works with.
"""

visitor.visit_concrete_component_a(self)

def exclusive_method_of_concrete_component_a(self) -> str:
"""
Concrete Components may have special methods that don't exist in their
base class or interface. The Visitor is still able to use these methods
since it's aware of the component's concrete class.
"""

return "A"


class ConcreteComponentB(Component):
"""
Same here: visitConcreteComponentB => ConcreteComponentB
"""

def accept(self, visitor: Visitor):
visitor.visit_concrete_component_b(self)

def special_method_of_concrete_component_b(self) -> str:
return "B"


class Visitor(ABC):
"""
The Visitor Interface declares a set of visiting methods that correspond to
component classes. The signature of a visiting method allows the visitor to
identify the exact class of the component that it's dealing with.
"""

@abstractmethod
def visit_concrete_component_a(self, element: ConcreteComponentA) -> None:
pass

@abstractmethod
def visit_concrete_component_b(self, element: ConcreteComponentB) -> None:
pass


"""
Concrete Visitors implement several versions of the same algorithm, which can
work with all concrete component classes.

You can experience the biggest benefit of the Visitor pattern when using it with
a complex object structure, such as a Composite tree. In this case, it might be
helpful to store some intermediate state of the algorithm while executing
visitor's methods over various objects of the structure.
"""


class ConcreteVisitor1(Visitor):
def visit_concrete_component_a(self, element) -> None:
print(f"{element.exclusive_method_of_concrete_component_a()} + ConcreteVisitor1")

def visit_concrete_component_b(self, element) -> None:
print(f"{element.special_method_of_concrete_component_b()} + ConcreteVisitor1")


class ConcreteVisitor2(Visitor):
def visit_concrete_component_a(self, element) -> None:
print(f"{element.exclusive_method_of_concrete_component_a()} + ConcreteVisitor2")

def visit_concrete_component_b(self, element) -> None:
print(f"{element.special_method_of_concrete_component_b()} + ConcreteVisitor2")


def client_code(components: List[Component], visitor: Visitor) -> None:
"""
The client code can run visitor operations over any set of elements without
figuring out their concrete classes. The accept operation directs a call to
the appropriate operation in the visitor object.
"""

# ...
for component in components:
component.accept(visitor)
# ...


if __name__ == "__main__":
components = [ConcreteComponentA(), ConcreteComponentB()]

print("The client code works with all visitors via the base Visitor interface:")
visitor1 = ConcreteVisitor1()
client_code(components, visitor1)

print("It allows the same client code to work with different types of visitors:")
visitor2 = ConcreteVisitor2()
client_code(components, visitor2)

Output.txt: 执行结果

1
2
3
4
5
6
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1
It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2

在 Ruby 中使用模式

复杂度: ★★★

流行度: ★☆☆

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

概念示例

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

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

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
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
# The Component interface declares an `accept` method that should take the base
# visitor interface as an argument.
class Component
# @abstract
#
# @param [Visitor] visitor
def accept(_visitor)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end

# Each Concrete Component must implement the `accept` method in such a way that
# it calls the visitor's method corresponding to the component's class.
class ConcreteComponentA < Component
# Note that we're calling `visitConcreteComponentA`, which matches the current
# class name. This way we let the visitor know the class of the component it
# works with.
def accept(visitor)
visitor.visit_concrete_component_a(self)
end

# Concrete Components may have special methods that don't exist in their base
# class or interface. The Visitor is still able to use these methods since
# it's aware of the component's concrete class.
def exclusive_method_of_concrete_component_a
'A'
end
end

# Same here: visit_concrete_component_b => ConcreteComponentB
class ConcreteComponentB < Component
# @param [Visitor] visitor
def accept(visitor)
visitor.visit_concrete_component_b(self)
end

def special_method_of_concrete_component_b
'B'
end
end

# The Visitor Interface declares a set of visiting methods that correspond to
# component classes. The signature of a visiting method allows the visitor to
# identify the exact class of the component that it's dealing with.
class Visitor
# @abstract
#
# @param [ConcreteComponentA] element
def visit_concrete_component_a(_element)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end

# @abstract
#
# @param [ConcreteComponentB] element
def visit_concrete_component_b(_element)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end

# Concrete Visitors implement several versions of the same algorithm, which can
# work with all concrete component classes.
#
# You can experience the biggest benefit of the Visitor pattern when using it
# with a complex object structure, such as a Composite tree. In this case, it
# might be helpful to store some intermediate state of the algorithm while
# executing visitor's methods over various objects of the structure.
class ConcreteVisitor1 < Visitor
def visit_concrete_component_a(element)
puts "#{element.exclusive_method_of_concrete_component_a} + #{self.class}"
end

def visit_concrete_component_b(element)
puts "#{element.special_method_of_concrete_component_b} + #{self.class}"
end
end

class ConcreteVisitor2 < Visitor
def visit_concrete_component_a(element)
puts "#{element.exclusive_method_of_concrete_component_a} + #{self.class}"
end

def visit_concrete_component_b(element)
puts "#{element.special_method_of_concrete_component_b} + #{self.class}"
end
end

# The client code can run visitor operations over any set of elements without
# figuring out their concrete classes. The accept operation directs a call to
# the appropriate operation in the visitor object.
def client_code(components, visitor)
# ...
components.each do |component|
component.accept(visitor)
end
# ...
end

components = [ConcreteComponentA.new, ConcreteComponentB.new]

puts 'The client code works with all visitors via the base Visitor interface:'
visitor1 = ConcreteVisitor1.new
client_code(components, visitor1)

puts 'It allows the same client code to work with different types of visitors:'
visitor2 = ConcreteVisitor2.new
client_code(components, visitor2)

output.txt: 执行结果

1
2
3
4
5
6
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1
It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2

在 Swift 中使用模式

复杂度: ★★★

流行度: ★☆☆

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

概念示例

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

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

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 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
94
95
96
97
98
99
100
101
102
103
104
105
106
import XCTest

/// The Component interface declares an `accept` method that should take the
/// base visitor interface as an argument.
protocol Component {

func accept(_ visitor: Visitor)
}

/// Each Concrete Component must implement the `accept` method in such a way
/// that it calls the visitor's method corresponding to the component's class.
class ConcreteComponentA: Component {

/// Note that we're calling `visitConcreteComponentA`, which matches the
/// current class name. This way we let the visitor know the class of the
/// component it works with.
func accept(_ visitor: Visitor) {
visitor.visitConcreteComponentA(element: self)
}

/// Concrete Components may have special methods that don't exist in their
/// base class or interface. The Visitor is still able to use these methods
/// since it's aware of the component's concrete class.
func exclusiveMethodOfConcreteComponentA() -> String {
return "A"
}
}

class ConcreteComponentB: Component {

/// Same here: visitConcreteComponentB => ConcreteComponentB
func accept(_ visitor: Visitor) {
visitor.visitConcreteComponentB(element: self)
}

func specialMethodOfConcreteComponentB() -> String {
return "B"
}
}

/// The Visitor Interface declares a set of visiting methods that correspond to
/// component classes. The signature of a visiting method allows the visitor to
/// identify the exact class of the component that it's dealing with.
protocol Visitor {

func visitConcreteComponentA(element: ConcreteComponentA)
func visitConcreteComponentB(element: ConcreteComponentB)
}

/// Concrete Visitors implement several versions of the same algorithm, which
/// can work with all concrete component classes.
///
/// You can experience the biggest benefit of the Visitor pattern when using it
/// with a complex object structure, such as a Composite tree. In this case, it
/// might be helpful to store some intermediate state of the algorithm while
/// executing visitor's methods over various objects of the structure.
class ConcreteVisitor1: Visitor {

func visitConcreteComponentA(element: ConcreteComponentA) {
print(element.exclusiveMethodOfConcreteComponentA() + " + ConcreteVisitor1\n")
}

func visitConcreteComponentB(element: ConcreteComponentB) {
print(element.specialMethodOfConcreteComponentB() + " + ConcreteVisitor1\n")
}
}

class ConcreteVisitor2: Visitor {

func visitConcreteComponentA(element: ConcreteComponentA) {
print(element.exclusiveMethodOfConcreteComponentA() + " + ConcreteVisitor2\n")
}

func visitConcreteComponentB(element: ConcreteComponentB) {
print(element.specialMethodOfConcreteComponentB() + " + ConcreteVisitor2\n")
}
}

/// The client code can run visitor operations over any set of elements without
/// figuring out their concrete classes. The accept operation directs a call to
/// the appropriate operation in the visitor object.
class Client {
// ...
static func clientCode(components: [Component], visitor: Visitor) {
// ...
components.forEach({ $0.accept(visitor) })
// ...
}
// ...
}

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

func test() {
let components: [Component] = [ConcreteComponentA(), ConcreteComponentB()]

print("The client code works with all visitors via the base Visitor interface:\n")
let visitor1 = ConcreteVisitor1()
Client.clientCode(components: components, visitor: visitor1)

print("\nIt allows the same client code to work with different types of visitors:\n")
let visitor2 = ConcreteVisitor2()
Client.clientCode(components: components, visitor: visitor2)
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
The client code works with all visitors via the base Visitor interface:

A + ConcreteVisitor1

B + ConcreteVisitor1


It allows the same client code to work with different types of visitors:

A + ConcreteVisitor2

B + ConcreteVisitor2

真实世界示例

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import Foundation
import XCTest


protocol Notification: CustomStringConvertible {

func accept(visitor: NotificationPolicy) -> Bool
}

struct Email {

let emailOfSender: String

var description: String { return "Email" }
}

struct SMS {

let phoneNumberOfSender: String

var description: String { return "SMS" }
}

struct Push {

let usernameOfSender: String

var description: String { return "Push" }
}

extension Email: Notification {

func accept(visitor: NotificationPolicy) -> Bool {
return visitor.isTurnedOn(for: self)
}
}

extension SMS: Notification {

func accept(visitor: NotificationPolicy) -> Bool {
return visitor.isTurnedOn(for: self)
}
}

extension Push: Notification {

func accept(visitor: NotificationPolicy) -> Bool {
return visitor.isTurnedOn(for: self)
}
}


protocol NotificationPolicy: CustomStringConvertible {

func isTurnedOn(for email: Email) -> Bool

func isTurnedOn(for sms: SMS) -> Bool

func isTurnedOn(for push: Push) -> Bool
}

class NightPolicyVisitor: NotificationPolicy {

func isTurnedOn(for email: Email) -> Bool {
return false
}

func isTurnedOn(for sms: SMS) -> Bool {
return true
}

func isTurnedOn(for push: Push) -> Bool {
return false
}

var description: String { return "Night Policy Visitor" }
}

class DefaultPolicyVisitor: NotificationPolicy {

func isTurnedOn(for email: Email) -> Bool {
return true
}

func isTurnedOn(for sms: SMS) -> Bool {
return true
}

func isTurnedOn(for push: Push) -> Bool {
return true
}

var description: String { return "Default Policy Visitor" }
}

class BlackListVisitor: NotificationPolicy {

private var bannedEmails = [String]()
private var bannedPhones = [String]()
private var bannedUsernames = [String]()

init(emails: [String], phones: [String], usernames: [String]) {
self.bannedEmails = emails
self.bannedPhones = phones
self.bannedUsernames = usernames
}

func isTurnedOn(for email: Email) -> Bool {
return bannedEmails.contains(email.emailOfSender)
}

func isTurnedOn(for sms: SMS) -> Bool {
return bannedPhones.contains(sms.phoneNumberOfSender)
}

func isTurnedOn(for push: Push) -> Bool {
return bannedUsernames.contains(push.usernameOfSender)
}

var description: String { return "Black List Visitor" }
}



class VisitorRealWorld: XCTestCase {

func testVisitorRealWorld() {

let email = Email(emailOfSender: "some@email.com")
let sms = SMS(phoneNumberOfSender: "+3806700000")
let push = Push(usernameOfSender: "Spammer")

let notifications: [Notification] = [email, sms, push]

clientCode(handle: notifications, with: DefaultPolicyVisitor())

clientCode(handle: notifications, with: NightPolicyVisitor())
}
}

extension VisitorRealWorld {

/// Client code traverses notifications with visitors and checks whether a
/// notification is in a blacklist and should be shown in accordance with a
/// current SilencePolicy

func clientCode(handle notifications: [Notification], with policy: NotificationPolicy) {

let blackList = createBlackList()

print("\nClient: Using \(policy.description) and \(blackList.description)")

notifications.forEach { item in

guard !item.accept(visitor: blackList) else {
print("\tWARNING: " + item.description + " is in a black list")
return
}

if item.accept(visitor: policy) {
print("\t" + item.description + " notification will be shown")
} else {
print("\t" + item.description + " notification will be silenced")
}
}
}

private func createBlackList() -> BlackListVisitor {
return BlackListVisitor(emails: ["banned@email.com"],
phones: ["000000000", "1234325232"],
usernames: ["Spammer"])
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
Client: Using Default Policy Visitor and Black List Visitor
Email notification will be shown
SMS notification will be shown
WARNING: Push is in a black list

Client: Using Night Policy Visitor and Black List Visitor
Email notification will be silenced
SMS notification will be shown
WARNING: Push is in a black list

在 TypeScript 中使用模式

复杂度: ★★★

流行度: ★☆☆

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

概念示例

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

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

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* The Component interface declares an `accept` method that should take the base
* visitor interface as an argument.
*/
interface Component {
accept(visitor: Visitor): void;
}

/**
* Each Concrete Component must implement the `accept` method in such a way that
* it calls the visitor's method corresponding to the component's class.
*/
class ConcreteComponentA implements Component {
/**
* Note that we're calling `visitConcreteComponentA`, which matches the
* current class name. This way we let the visitor know the class of the
* component it works with.
*/
public accept(visitor: Visitor): void {
visitor.visitConcreteComponentA(this);
}

/**
* Concrete Components may have special methods that don't exist in their
* base class or interface. The Visitor is still able to use these methods
* since it's aware of the component's concrete class.
*/
public exclusiveMethodOfConcreteComponentA(): string {
return 'A';
}
}

class ConcreteComponentB implements Component {
/**
* Same here: visitConcreteComponentB => ConcreteComponentB
*/
public accept(visitor: Visitor): void {
visitor.visitConcreteComponentB(this);
}

public specialMethodOfConcreteComponentB(): string {
return 'B';
}
}

/**
* The Visitor Interface declares a set of visiting methods that correspond to
* component classes. The signature of a visiting method allows the visitor to
* identify the exact class of the component that it's dealing with.
*/
interface Visitor {
visitConcreteComponentA(element: ConcreteComponentA): void;

visitConcreteComponentB(element: ConcreteComponentB): void;
}

/**
* Concrete Visitors implement several versions of the same algorithm, which can
* work with all concrete component classes.
*
* You can experience the biggest benefit of the Visitor pattern when using it
* with a complex object structure, such as a Composite tree. In this case, it
* might be helpful to store some intermediate state of the algorithm while
* executing visitor's methods over various objects of the structure.
*/
class ConcreteVisitor1 implements Visitor {
public visitConcreteComponentA(element: ConcreteComponentA): void {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor1`);
}

public visitConcreteComponentB(element: ConcreteComponentB): void {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor1`);
}
}

class ConcreteVisitor2 implements Visitor {
public visitConcreteComponentA(element: ConcreteComponentA): void {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor2`);
}

public visitConcreteComponentB(element: ConcreteComponentB): void {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor2`);
}
}

/**
* The client code can run visitor operations over any set of elements without
* figuring out their concrete classes. The accept operation directs a call to
* the appropriate operation in the visitor object.
*/
function clientCode(components: Component[], visitor: Visitor) {
// ...
for (const component of components) {
component.accept(visitor);
}
// ...
}

const components = [
new ConcreteComponentA(),
new ConcreteComponentB(),
];

console.log('The client code works with all visitors via the base Visitor interface:');
const visitor1 = new ConcreteVisitor1();
clientCode(components, visitor1);
console.log('');

console.log('It allows the same client code to work with different types of visitors:');
const visitor2 = new ConcreteVisitor2();
clientCode(components, visitor2);

Output.txt: 执行结果

1
2
3
4
5
6
7
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1

It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2

概念示例

访问者模式允许你在结构体中添加行为, 而又不会对结构体造成实际变更。 假设你是一个代码库的维护者, 代码库中包含不同的形状结构体, 如:

  • 方形
  • 圆形
  • 三角形

上述每个形状结构体都实现了通用形状接口。

在公司员工开始使用你维护的代码库时, 你就会被各种功能请求给淹没。 让我们来看看其中比较简单的请求: 有个团队请求你在形状结构体中添加 get­Area获取面积行为。

解决这一问题的办法有很多。

第一个选项便是将 get­Area方法直接添加至形状接口, 然后在各个形状结构体中进行实现。 这似乎是比较好的解决方案, 但其代价也比较高。 作为代码库的管理员, 相信你也不想在每次有人要求添加另外一种行为时就去冒着风险改动自己的宝贝代码。 不过, 你也一定想让其他团队的人还是用一用自己的代码库。

第二个选项是请求功能的团队自行实现行为。 然而这并不总是可行, 因为行为可能会依赖于私有代码。

第三个方法就是使用访问者模式来解决上述问题。 首先定义一个如下访问者接口:

1
2
3
4
5
type visitor interface {
visitForSquare(square)
visitForCircle(circle)
visitForTriangle(triangle)
}

我们可以使用 visit­For­Square­(square)visit­For­Circle­(circle)以及 visit­For­Triangle­(triangle)函数来为方形、 圆形以及三角形添加相应的功能。

你可能在想, 为什么我们不再访问者接口里面使用单一的 visit­(shape)方法呢? 这是因为 Go 语言不支持方法重载, 所以你无法以相同名称、 不同参数的方式来使用方法。

好了, 第二项重要的工作是将 accept接受方法添加至形状接口中。

1
func accept(v visitor)

所有形状结构体都需要定义此方法, 类似于:

1
2
3
func (obj *square) accept(v visitor){
v.visitForSquare(obj)
}

等等, 我刚才是不是提到过, 我们并不想修改现有的形状结构体? 很不幸, 在使用访问者模式时, 我们必须要修改形状结构体。 但这样的修改只需要进行一次。

如果添加任何其他行为, 比如 get­Num­Sides获取边数和 get­Middle­Coordinates获取中点坐标 , 我们将使用相同的 accept­(v visitor)函数, 而无需对形状结构体进行进一步的修改。

最后, 形状结构体只需要修改一次, 并且所有未来针对不同行为的请求都可以使用相同的 accept 函数来进行处理。 如果团队成员请求 get­Area行为, 我们只需简单地定义访问者接口的具体实现, 并在其中编写面积的计算逻辑即可。

shape.go: 元件

1
2
3
4
5
6
package main

type shape interface {
getType() string
accept(visitor)
}

square.go: 具体元件

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

type square struct {
side int
}

func (s *square) accept(v visitor) {
v.visitForSquare(s)
}

func (s *square) getType() string {
return "Square"
}

circle.go: 具体元件

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

type circle struct {
radius int
}

func (c *circle) accept(v visitor) {
v.visitForCircle(c)
}

func (c *circle) getType() string {
return "Circle"
}

rectangle.go: 具体元件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

type rectangle struct {
l int
b int
}

func (t *rectangle) accept(v visitor) {
v.visitForrectangle(t)
}

func (t *rectangle) getType() string {
return "rectangle"
}

visitor.go: 访问者

1
2
3
4
5
6
7
package main

type visitor interface {
visitForSquare(*square)
visitForCircle(*circle)
visitForrectangle(*rectangle)
}

areaCalculator.go: 具体访问者

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

import (
"fmt"
)

type areaCalculator struct {
area int
}

func (a *areaCalculator) visitForSquare(s *square) {
// Calculate area for square.
// Then assign in to the area instance variable.
fmt.Println("Calculating area for square")
}

func (a *areaCalculator) visitForCircle(s *circle) {
fmt.Println("Calculating area for circle")
}
func (a *areaCalculator) visitForrectangle(s *rectangle) {
fmt.Println("Calculating area for rectangle")
}

middleCoordinates.go: 具体访问者

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

import "fmt"

type middleCoordinates struct {
x int
y int
}

func (a *middleCoordinates) visitForSquare(s *square) {
// Calculate middle point coordinates for square.
// Then assign in to the x and y instance variable.
fmt.Println("Calculating middle point coordinates for square")
}

func (a *middleCoordinates) visitForCircle(c *circle) {
fmt.Println("Calculating middle point coordinates for circle")
}
func (a *middleCoordinates) visitForrectangle(t *rectangle) {
fmt.Println("Calculating middle point coordinates for rectangle")
}

main.go: 客户端代码

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

import "fmt"

func main() {
square := &square{side: 2}
circle := &circle{radius: 3}
rectangle := &rectangle{l: 2, b: 3}

areaCalculator := &areaCalculator{}

square.accept(areaCalculator)
circle.accept(areaCalculator)
rectangle.accept(areaCalculator)

fmt.Println()
middleCoordinates := &middleCoordinates{}
square.accept(middleCoordinates)
circle.accept(middleCoordinates)
rectangle.accept(middleCoordinates)
}

output.txt: 执行结果

1
2
3
4
5
6
7
Calculating area for square
Calculating area for circle
Calculating area for rectangle

Calculating middle point coordinates for square
Calculating middle point coordinates for circle
Calculating middle point coordinates for rectangle

根据: Golang By Example