访问者模式

访问者模式

一、什么是访问者模式?

访问者模式(Visitor Design Patten):将作用于数据结构中的操作分离出来,形成单独的操作类,这样就可以在不改变数据结构的情况下,扩展出新的操作。

它将数据结构与数据处理分离开来,保持数据结构不变,而允许扩展新的处理。

二、访问者模式的应用场景是什么?

  • 被处理的数据结构相对稳定
  • 被处理的数据结构要求保持不变,尽量避免数据处理影响到数据结构
  • 数据处理类型比较多,相对复杂,并且数据处理数量是可变化的,可扩展的

就比如说文件,文件类型虽然多,但是常用的也就那几种文件,如PPT、Word、Pdf等。

总的来说,文件数据类型相对来说比较稳定,不容易变动。

但是对文件的操作就比较频繁了,比如对文件内容搜索、分页、调整样式等,有各种各样的操作,是变化的。

这种情况下,使用访问者模式将文件和操作分离,很容易就能添加新的操作行为。

三、访问者模式的结构是怎么样的?

访问者模式中的登场角色:

  • 访问者(Visitor):负责对数据结构中的每个具体元素(ConcreteElement)声明访问接口,表示访问该元素
  • 具体的访问者(ConcreteVisitor):负责实现Visitor接口
  • 元素(Element):表示被Visitor访问的对象,声明了接受Visitor访问的接口
  • 具体元素(ConcreteElement):负责实现Element接口
  • 对象结构(ObjectStructure):负责处理Element角色的集合

访问者模式结构

示例程序结构:

访问者模式结构

Viitor 和 Element:

1
2
3
4
public interface Visitor {
void visit(File file);
void visit(Directory directory);
}
1
2
3
public interface Element {
void accept(Visitor visitor);
}

Entry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class Entry implements Element {

public abstract String getName();
public abstract int getSize();

public Entry addEntry(Entry entry) {
throw new FileTreatmentException();
}

public Iterator iterator() {
throw new FileTreatmentException();
}

@Override
public String toString() {
return getName() + " (" + getSize() + ")";
}
}
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 File extends Entry {

private String name;
private int size;

public File(String name, int size) {
this.name = name;
this.size = size;
}

@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}

@Override
public String getName() {
return name;
}

@Override
public int getSize() {
return size;
}
}
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
public class Directory extends Entry {

private String name;
private ArrayList<Entry> dir = new ArrayList<>();

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

@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}

@Override
public Entry addEntry(Entry entry) {
dir.add(entry);
return this;
}

@Override
public Iterator iterator() {
return dir.iterator();
}

@Override
public String getName() {
return name;
}

@Override
public int getSize() {
int size = 0;
Iterator<Entry> it = dir.iterator();
while (it.hasNext()) {
Entry entry = it.next();
size += entry.getSize();
}
return size;
}
}

ListVisitor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ListVisitor implements Visitor {

private String currentDir = "";

@Override
public void visit(File file) {
System.out.println(currentDir + "/" + file);
}

@Override
public void visit(Directory directory) {
System.out.println(currentDir + "/" + directory);
String saveDir = currentDir;
currentDir = currentDir + "/" + directory.getName();
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
entry.accept(this);
}
currentDir = saveDir;
}
}

四、访问者模式有什么优缺点?

优点

  • 可扩展性好。将数据与处理分离,方便增加新的处理
  • 符合单一职责原则。每种操作都被封装成同一类的访问者,功能都比较单一

缺点:

  • 可读性略差:访问者模式的代码结构是略微有点绕的,可读性和可理解性有点差
  • 破坏依赖倒置原则。访问者依赖了具体的数据结构类型,而不是抽象数据结构
  • 增加新的数据结构类麻烦。访问者是依赖了具体的数据结构的,增加一种新的数据结构,需要在访问者种增加新的处理接口

五、关于访问者的一些问题

5.1 访问者模式将数据与操作分离,是否违背面向对象设计原则?

  • 从表面上看,分离数据和操作,是违背了面向对象设计原则的
  • 从另一角度看,操作也可以认为是对象,访问者模式只是将数据对象和操作对象组合起来了
  • 访问者结构中,数据是属于变化比较少的,而操作则是属于变化比较多的
  • 访问者模式,将不变部分(数据)和可变部分(操作)分离,使用组合方式关联,是符合面向对象设计的

5.2 访问者模式的替代方案?

  • 可使用工厂方法模式替代
  • 每种操作类型,对应一个操作工厂类,为不同的数据类型生成不同的操作对象
  • 比如文件Word、Pdf,抽取操作对应一个工厂ExtractFactory,为不同数据类型提供不同的实现WordExtractor、PdfExtractor
  • 新增压缩操作Comoress,定义一个工厂CompressFactory,提供不同的操作实现WordCompressor、PdfCompressor

5.3 什么时候用访问者模式?什么时候用工厂方法模式?

具体的使用场景参考:

  • 垂直扩展较多,即数据类型可变时,使用工厂模式比较合适
  • 水平扩展较多,即操作类型可变时,使用访问者模式比较合适
  • 垂直、水平扩展都比较多,即数据和操作都可变时,可使用模板模式 + 工厂模式/访问者模式

(1) 操作变化少,而数据类型经常变动,如添加新文件类型Txt、Excel等

  • 使用访问者模式,就需要修改已有的每种操作类型,不符合开闭原则
  • 使用工厂模式,只需要增加新的工厂类和操作类,无需修改原来的代码,符合开闭原则

(2) 数据类型基本不变,但是操作变化多,如为文件添加搜索、过滤、样式等操作

  • 使用工厂模式,就需要为每种操作都添加工厂类和操作实现类,虽然符合开闭原则,但是类结构变得很复杂了,类数量也增多了
  • 使用访问者模式,只需要添加新的操作类即可,符合开闭原则,而且可以避免增加很多类

(3) 数据类型和操作类型都可变,如文件可扩展Txt、Excel,操作也可扩展搜索、过滤等

  • 在数据类型垂直扩展上,可以使用模板模式抽取公共的代码到父类中,避免重复代码
  • 在操作类型水平扩展上,如果操作数量不多,类数量不多,可使用工厂模式实现
  • 在操作类型水平扩展上,如果操作数量很多,可使用访问者模式实现,避免类数量过多
作者

jiaduo

发布于

2022-01-15

更新于

2023-04-03

许可协议