Java 设计模式——访问者模式

模式定义

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

模式示例

下面以电脑启动时对各个组成部分的访问为例来说明访问者模式。主要以下几个步骤

  1. 定义元素接口,被访问者需要实现这个接口;
  2. 定义访问者接口,访问者需要实现该接口;
  3. 具体实现被访问者类和访问者类
  4. 编写客户端验证

定义元素接口

1
2
3
4
5
6
7
8
/**
* 定义元素接口
*/
public interface ComputerPart {
void accept(ComputerPartVisitor computerPartVisitor);

void accept(UserInputVisitor userInputVisitor);
}

定义访问者接口

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 定义访问者接口
*/
public interface ComputerPartVisitor {
void visit(Computer computer);

void visit(Mouse mouse);

void visit(Keyboard keyboard);

void visit(Monitor monitor);
}

这里给访问者传递的都是具体的类,如果被访问者新增元素,需要增加相应的方法,同时访问者类的实现者需要实现该方法,违反了迪米特原则。

具体实现被访问者和访问者

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
/**
* 电脑(结构固定的元素对象)
*/
public class Computer implements ComputerPart {

ComputerPart[] parts;

public Computer(){
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}

@Override
public void accept(UserInputVisitor userInputVisitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(userInputVisitor);
}
userInputVisitor.visit(this);
}
}

这是元素对象,本身可被访问,同时其组成部分也可被访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 键盘
*/
public class Keyboard implements ComputerPart {

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}

@Override
public void accept(UserInputVisitor userInputVisitor) {
userInputVisitor.visit(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 显示器
*/
public class Monitor implements ComputerPart {

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}

@Override
public void accept(UserInputVisitor userInputVisitor) {
userInputVisitor.visit(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 鼠标
*/
public class Mouse implements ComputerPart {

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}

@Override
public void accept(UserInputVisitor userInputVisitor) {
userInputVisitor.visit(this);
}
}

定义访问者,先看整体访问者

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
/**
* 整体访问者
*/
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {

@Override
public void visit(Computer computer) {
System.out.println("Displaying Computer.");
}

@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}

@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}

@Override
public void visit(Monitor monitor) {
System.out.println("Displaying Monitor.");
}
}

输入设备访问者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 输入设备访问者
*/
public class UserInputVisitor implements ComputerPartVisitor {

@Override
public void visit(Computer computer) {}

@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}

@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}

@Override
public void visit(Monitor monitor) {}
}

用来验证的客户端

1
2
3
4
5
6
7
8
9
10
11
12
public class VisitorPatternDemo {
public static void main(String[] args) {

ComputerPart computer = new Computer();

System.out.println("== Show all computer part: ==");
computer.accept(new ComputerPartDisplayVisitor());

System.out.println("\n== Show user input part: ==");
computer.accept(new UserInputVisitor());
}
}

输出结果:

1
2
3
4
5
6
7
8
9
== Show all computer part: ==
Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.

== Show user input part: ==
Displaying Mouse.
Displaying Keyboard.

可以看到,访问者还是可能会有一些方法的冗余的,比如UserInputVisitor,不需要访问MonitorComputer但却不得不实现访问者接口定义的方法(空实现)

模式总结

使用访问者模式时,通常这个类(元素类)的结构(数据结构)比较固定,主要解决稳定的数据结构和易变的操作耦合问题。

优缺点

  • 优点
    1. 符合单一职责原则。
    2. 优秀的扩展性。
    3. 灵活性。
  • 缺点
    • 具体元素对访问者公布细节,违反了迪米特原则(一个对象应该尽可能的减少对其他元素的了解)。
    • 具体元素变更比较困难。
    • 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

使用场景

对固定多部分组成对象元素进行不同的操作时可以用到该模式。如电脑的结构固定,我可以定义一个访问者只访问输入设备,也可以定义一个访问者访问显示设备。

  1. 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,也不希望在增加新操作时修改这些类。

注意事项

  1. 如果元素经常变动,结构不固定,就不适合使用访问者模式了,因为该模式增加新元素比较困难;
  2. 访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
  3. 在数据基础类里面有一个方法接受访问者,将自身引用传入访问者,已让访问者访问。
  4. 在被访问的类里面加一个对外提供接待访问者的接口。