设计模式--行为型:访问者模式

访问者模式:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义于作用于这些元素的新操作

分派

参考《深入理解 Java 虚拟机:JVM 高级特性与最佳实践 第 2 版》 第 8.3.2 章:分派,这一章中介绍了分派的概念,以及静态分派和动态分派的含义,给出的结论是 Java 是一门静态多分派和动态单分派的语言。
具体分析也可以参考:Java 面向对象的特征 – 方法调用:分派

访问者模式

访问者模式 Visitor Pattern:把数据处理和数据结构分离。在数据结构相对稳定时,访问者模式使得数据处理的算法修改或增加都变得非常容易。
访问者模式使用两次动态单分派来间接实现伪动态双分派,具体可以参考类图及代码分析。

类图结构

0070-Visitor-uml-classdiag.png

结构解析

  • Element
    抽象类,元素,定义一个 accept 方法,表示每个元素都能被访问者访问。
  • Visitor
    抽象类,访问者,定义对每一个元素访问的行为,它的参数就是可以访问的元素。虚方法个数理论上和元素子类个数是一致的,表示访问每一个元素。
  • ConcreteElement
    实现类,元素中 accept 的具体实现。实现格式很固定,就是调用 Visitor 中访问该具体元素的方法。充分利用双分派技术,实现数据处理和数据结构的分离。
  • ConcreteVisitor
    实现类,定义访问者访问某个元素时具体的行为。
  • ObjectStructure
    定义一个数据结构对象,管理元素的集合,并能够让访问者遍历访问每个元素。
  • Client
    客户端。

访问者模式中 ObjectStructure, Element, ConcreteElement 三者很像一个观察者模式。访问者模式的优势在与数据处理和数据结构的分离,其中数据结构即为 Element ,数据处理即为 Visitor。修改数据的处理方式非常简单,但是如果增加数据结构将会变得很麻烦。

示例

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
// 1. Element
public abstract class Element {
public abstract void accept(Visitor visitor);
}

// 2. ConcreteElement
public class ConcreteElementA extends Element{
@Override
public void accept(Visitor visitor) {
// 实现第二次动态分派
visitor.visitConcreteElementA(this);
}
}

public class ConcreteElementB extends Element{
@Override
public void accept(Visitor visitor) {
// 实现第二次动态分派
visitor.visitConcreteElementB(this);
}
}

// 3. Visitor
public abstract class Visitor {
public abstract void visitConcreteElementA(ConcreteElementA elementA);
public abstract void visitConcreteElementB(ConcreteElementB elementB);
}

// 4. ConcreteVisitor
public class ConcreteVisitor1 extends Visitor{

@Override
public void visitConcreteElementA(ConcreteElementA elementA) {
System.out.println("ConcreteVisitor1 visit ConcreteElementA.");
}

@Override
public void visitConcreteElementB(ConcreteElementB elementB) {
System.out.println("ConcreteVisitor1 visit ConcreteElementB.");
}
}

public class ConcreteVisitor2 extends Visitor{
@Override
public void visitConcreteElementA(ConcreteElementA elementA) {
System.out.println("ConcreteVisitor2 visit ConcreteElementA.");
}

@Override
public void visitConcreteElementB(ConcreteElementB elementB) {
System.out.println("ConcreteVisitor2 visit ConcreteElementB.");
}
}

// 5. ObjectStructure
public class ObjectStructure {
List<Element> elements = new ArrayList<>();

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

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

public void accept(Visitor visitor){
for (Element element : elements){
// 第一次动态分派
element.accept(visitor);
}
}
}

// 6. Test
public class TestVisitor {
public static void main(String[] args) {
Element elementA = new ConcreteElementA();
Element elementB = new ConcreteElementB();

ObjectStructure objectStructure = new ObjectStructure();
objectStructure.add(elementA);
objectStructure.add(elementB);

Visitor visitor1 = new ConcreteVisitor1();
Visitor visitor2 = new ConcreteVisitor2();
objectStructure.accept(visitor1);
objectStructure.accept(visitor2);
}
}

// 7. Result
ConcreteVisitor1 visit ConcreteElementA.
ConcreteVisitor1 visit ConcreteElementB.
ConcreteVisitor2 visit ConcreteElementA.
ConcreteVisitor2 visit ConcreteElementB.

示例中可以看到,每次接收一个访问者,该访问者都会遍历所有的元素。

访问者模式和双分派

双分派体现在 accept 这个方法上:

  • 第一次动态分派
    ObjectStructure.accept 中遍历执行 Element.accept 时,并不确定接受者具体是哪个 ConcreteVisitor1/ConcreteVisitor2,运行时确定。
  • 第二次动态分派
    Element.accept 中,Visitor 并不确定参数具体是哪个 ConcreteElementA/ConcreteElementB,运行时确定。

组合实现了双分派,只有在运行时才能确定是哪个 Visitor 访问哪个 Element

总结

访问者模式的几个特点:

  • 把数据结构(Element)和作用于结构上的操作(Visitor)解耦合,使得操作集合可相对自由地演化
  • 适用于数据结构相对稳定算法又易变化的系统,算法操作增加很容易
  • 优点是增加操作很容易,其缺点就是增加新的元素会非常困难

参考文档

  • 深入理解Java虚拟机:JVM高级特性与最佳实践 第2版
  • 大话设计模式
  • Android 源码设计模式解析与实战
  • 分派
  • 访问模式和双分派
0%