alexpdh's blog

Java设计模式(二十五):访问者模式

访问者模式(Visitor Pattern)

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


访问者模式UML图

visitor-pattern.jpg


访问者模式涉及的角色

抽象访问者(Visitor)角色:为该对象结构中 ConcreteElement 的每一个类声明一个 visite 操作。
具体访问者(ConcreteVisitor)角色:实现每个由 visitor 声明的操作。每个操作实现算法的一部分,而该算法片段乃是对应于结构中的对象的类。
抽象元素(Element)角色:定义一个 accept 操作,它以一个访问者为参数。
具体元素(ConcreteElement)角色:实现了抽象元素所规定的接受操作。
结构对象(ObjectStructure)角色:有如下的责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如List或Set。


示例代码

抽象访问者类 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
package com.example.visitorPattern;
/**
* 访问者模式:访问者类
*
* @author pengdh
* @date: 2017-08-19 17:24
*/
public abstract class Visitor {
/**
* 对应于 ConcreteElementA 的访问操作
*
* @param concreteElementA 具体元素 A
*/
public abstract void visitConcreteElementA(ConcreteElementA concreteElementA);
/**
* 对应于 ConcreteElementB 的访问操作
*
* @param concreteElementB 具体元素 B
*/
public abstract void visitConcreteElementB(ConcreteElementB concreteElementB);
}

具体访问者1类 ConcreteVisitor1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.visitorPattern;
/**
* 访问者模式:具体访问者1
*
* @author pengdh
* @date: 2017-08-19 17:35
*/
public class ConcreteVisitor1 extends Visitor {
@Override
public void visitConcreteElementA(ConcreteElementA concreteElementA) {
System.out.println(ConcreteElementA.class.getSimpleName() + " 被 " + this.getClass().getSimpleName() + " 访问!");
}
@Override
public void visitConcreteElementB(ConcreteElementB concreteElementB) {
System.out.println(ConcreteElementB.class.getSimpleName() + " 被 " + this.getClass().getSimpleName() + " 访问!");
}
}

具体访问者2类 ConcreteVisitor2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.visitorPattern;
/**
* 访问者模式:具体访问者2
*
* @author pengdh
* @date: 2017-08-19 17:36
*/
public class ConcreteVisitor2 extends Visitor {
@Override
public void visitConcreteElementA(ConcreteElementA concreteElementA) {
System.out.println(ConcreteElementA.class.getSimpleName() + " 被 " + this.getClass().getSimpleName() + " 访问!");
}
@Override
public void visitConcreteElementB(ConcreteElementB concreteElementB) {
System.out.println(ConcreteElementB.class.getSimpleName() + " 被 " + this.getClass().getSimpleName() + " 访问!");
}
}

抽象元素类 Element

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.visitorPattern;
/**
* 访问者模式:抽象元素类
*
* @author pengdh
* @date: 2017-08-19 17:25
*/
public abstract class Element {
/**
* 接收操作
*
* @param visitor 访问者类
*/
public abstract void accept(Visitor visitor);
}

具体元素类A ConcreteElementA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.visitorPattern;
/**
* 访问者模式:具体元素类A
*
* @author pengdh
* @date: 2017-08-19 17:28
*/
public class ConcreteElementA extends Element {
@Override
public void accept(Visitor visitor) {
visitor.visitConcreteElementA(this);
}
/**
* ConcreteElementA 特有方法
*/
public void operationA() {
}
}

具体元素类B ConcreteElementB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.visitorPattern;
/**
* 访问者模式:具体元素类B
*
* @author pengdh
* @date: 2017-08-19 17:29
*/
public class ConcreteElementB extends Element {
@Override
public void accept(Visitor visitor) {
visitor.visitConcreteElementB(this);
}
/**
* ConcreteElementB 特有方法
*/
public void operationB() {
}
}

结构对象类 ObjectStructure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.example.visitorPattern;
import java.util.ArrayList;
import java.util.List;
/**
* 访问者模式:结构对象类
*
* @author pengdh
* @date: 2017-08-19 20:05
*/
public class ObjectStructure {
private List<Element> elements = new ArrayList<Element>();
/**
* 添加元素
*
* @param element
*/
public void attach(Element element) {
elements.add(element);
}
/**
* 移除元素
* @param element
*/
public void deatch(Element element) {
elements.remove(element);
}
/**
* 执行方法
*
* @param visitor
*/
public void accept(Visitor visitor) {
for (Element e : elements) {
e.accept(visitor);
}
}
}

客户端测试类 VisitorPatternTest

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
package com.example.visitorPattern;
/**
* 访问者模式:客户端测试类
*
* @author pengdh
* @date: 2017-08-19 20:04
*/
public class VisitorPatternTest {
public static void main(String[] args) {
// 创建一个结构对象
ObjectStructure o = new ObjectStructure();
// 给结构添加元素
o.attach(new ConcreteElementA());
o.attach(new ConcreteElementB());
// 创建访问者
Visitor v1 = new ConcreteVisitor1();
Visitor v2 = new ConcreteVisitor2();
o.accept(v1);
o.accept(v2);
}
}

执行过程

  1. 客户端创建一个结构对象 ObjectStructure,将一个新的 ConcreteElementA 和一个新的 ConcreteElementB传入。
  2. 客户端创建一个 ConcreteVisitor1 和一个 ConcreteVisitor2 对象,并将对应的两个对象传入结构对象中。
  3. 客户端调用结构对象聚集管理方法,将 ConcreteElementA 和 ConcreteElementB 节点加入到结构对象中去。
  4. 客户端调用结构对象的接受方法 accept(),启动访问过程。
  5. ConcreteElementA 对象的接受方法 accept() 被调用,并将 ConcreteVisitor1 对象本身传入;
  6. ConcreteElementA 对象反过来调用 ConcreteVisitor1 对象的访问方法,并将 ConcreteElementA 对象本身传入;
  7. ConcreteVisitor1 对象调用 输出对应信息。
  8. ConcreteElementB 与 ConcreteVisitor2 执行过程类似。

双重分派

宗量:一个方法所属的对象叫做方法的接收者,方法的接收者与方法的参数统称做方法的宗量。
双重分派:一个方法根据两个宗量的类型来决定执行不同的代码,这就是“双重分派”。

  • 上面的 5 6 7 8 步骤就是一个双重分派过程。

访问者模式的优点

好的扩展性:能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
好的复用性:可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
分离无关行为: 可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。


访问者模式的缺点

对象结构变化很困难:不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高。
破坏封装:访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructrue,这破坏了对象的封装性。


参考文献

alexpdh wechat
欢迎扫一扫关注 程序猿pdh 公众号!