设计模式之访问者模式

1. 概述

访问者模式 是一种行为型设计模式,它允许在不改变对象结构的前提下,定义作用于这些对象结构中各个元素的新操作。其核心思想是将数据结构作用于结构上的操作解耦,使得操作集合可以相对自由地演化。 访问者模式适用于当一个对象结构包含很多类对象,而你又想对这些对象实施一些依赖于其具体类的操作时。

2. 模式意图

  • 解耦数据结构和操作: 将作用于对象结构上的操作从对象自身中分离出来,使得操作可以独立变化。
  • 增加新的操作: 允许在不修改对象结构的前提下,增加新的操作。
  • 操作的集中化: 将相关的操作集中到一个访问者类中,便于管理和维护。
  • 支持多态访问: 可以根据不同的对象类型,执行不同的操作。

3. 模式结构

访问者模式主要包含以下角色:

  • 访问者 (Visitor):
    • 声明一个访问操作接口,该接口为对象结构中每一个 ConcreteElement 都有一个 visit() 方法。
  • 具体访问者 (ConcreteVisitor):
    • 实现 Visitor 声明的接口,也就是实际对 ConcreteElement 进行操作的类。
  • 元素 (Element):
    • 定义一个 accept() 方法,接受一个访问者对象作为参数。
  • 具体元素 (ConcreteElement):
    • 实现 Element 接口,代表被访问的具体对象。
    • 通常在 accept() 方法中调用访问者的 visit() 方法,将自身引用作为参数传递给访问者。
  • 对象结构 (Object Structure):
    • 是一个包含元素的集合,提供一个高层接口,允许访问者访问它的元素。
    • 可以是一个列表、一个树或其他任何复杂的结构。

4. 优缺点

优点:

  • 符合单一职责原则: 将与对象结构相关的访问行为集中到了访问者类中,符合单一职责原则。
  • 扩展性良好: 可以很容易地添加新的访问者,从而扩展新的操作。
  • 对象结构稳定时适用: 当对象结构不经常变化时,访问者模式可以很好地将操作从对象结构中分离出来。
  • 便于算法复用: 可以将相同的算法应用于不同的对象结构。

缺点:

  • 对象结构变化时维护困难: 如果对象结构发生变化,例如增加或删除元素,则需要修改所有访问者类,维护成本较高。
  • 破坏封装性: 访问者需要访问元素的内部状态,可能会破坏元素的封装性。
  • 具体元素对访问者依赖性强: 具体元素必须提供 accept 方法来接受访问者,增加了元素对访问者的依赖性。

5. 适用场景

  • 一个对象结构包含很多类对象,而你又想对这些对象实施一些依赖于其具体类的操作。
  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
  • 当对象结构很少改变,但经常需要在此结构上定义新的操作时。

6. 示例代码

场景:

假设你正在开发一个动物园管理系统。 你有不同种类的动物(例如,狮子、猴子、海豚),并且你想要对这些动物执行不同的操作(例如,喂食、玩耍、检查健康状况)。 使用访问者模式,你可以在不修改动物类的前提下,动态地添加这些操作。

1. 动物接口 (Element Interface)

1
2
3
4
// 动物接口,定义了接受访问者的方法
interface Animal {
void accept(AnimalVisitor visitor);
}

2. 具体动物类 (Concrete Element)

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
// 具体动物类,实现了 Animal 接口
class Lion implements Animal {
@Override
public void accept(AnimalVisitor visitor) {
visitor.visit(this); // 接受访问者,并将自身作为参数传递
}

public void roar() {
System.out.println("狮子咆哮!");
}
}

class Monkey implements Animal {
@Override
public void accept(AnimalVisitor visitor) {
visitor.visit(this); // 接受访问者,并将自身作为参数传递
}

public void climb() {
System.out.println("猴子爬树!");
}
}

class Dolphin implements Animal {
@Override
public void accept(AnimalVisitor visitor) {
visitor.visit(this); // 接受访问者,并将自身作为参数传递
}

public void swim() {
System.out.println("海豚游泳!");
}
}

3. 动物访问者接口 (Visitor Interface)

1
2
3
4
5
6
// 动物访问者接口,定义了访问不同种类动物的方法
interface AnimalVisitor {
void visit(Lion lion);
void visit(Monkey monkey);
void visit(Dolphin dolphin);
}

4. 具体动物访问者类 (Concrete 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
// 具体动物访问者类,实现了 AnimalVisitor 接口,定义了具体的访问行为
class FeedVisitor implements AnimalVisitor {
@Override
public void visit(Lion lion) {
System.out.println("喂狮子吃肉!");
}

@Override
public void visit(Monkey monkey) {
System.out.println("喂猴子吃香蕉!");
}

@Override
public void visit(Dolphin dolphin) {
System.out.println("喂海豚吃鱼!");
}
}

class PlayVisitor implements AnimalVisitor {
@Override
public void visit(Lion lion) {
System.out.println("和狮子玩耍(小心点!)");
lion.roar();
}

@Override
public void visit(Monkey monkey) {
System.out.println("和猴子玩耍,给它挠痒痒!");
monkey.climb();
}

@Override
public void visit(Dolphin dolphin) {
System.out.println("和海豚玩耍,一起跳跃!");
dolphin.swim();
}
}

class HealthCheckVisitor implements AnimalVisitor {
@Override
public void visit(Lion lion) {
System.out.println("检查狮子的健康状况...");
}

@Override
public void visit(Monkey monkey) {
System.out.println("检查猴子的健康状况...");
}

@Override
public void visit(Dolphin dolphin) {
System.out.println("检查海豚的健康状况...");
}
}

5. 对象结构 (Object Structure)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.ArrayList;
import java.util.List;

class Zoo {
private List<Animal> animals = new ArrayList<>();

public void addAnimal(Animal animal) {
animals.add(animal);
}

public void removeAnimal(Animal animal) {
animals.remove(animal);
}

public void accept(AnimalVisitor visitor) {
for (Animal animal : animals) {
animal.accept(visitor);
}
}
}

6. 客户端 (Client)

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
// 客户端
public class VisitorPatternDemo {
public static void main(String[] args) {
// 创建动物对象
Lion lion = new Lion();
Monkey monkey = new Monkey();
Dolphin dolphin = new Dolphin();

// 创建动物园
Zoo zoo = new Zoo();
zoo.addAnimal(lion);
zoo.addAnimal(monkey);
zoo.addAnimal(dolphin);

// 创建访问者对象
FeedVisitor feedVisitor = new FeedVisitor();
PlayVisitor playVisitor = new PlayVisitor();
HealthCheckVisitor healthCheckVisitor = new HealthCheckVisitor();

// 让动物园接受访问者
System.out.println("--- 喂食 ---");
zoo.accept(feedVisitor);

System.out.println("--- 玩耍 ---");
zoo.accept(playVisitor);

System.out.println("--- 健康检查 ---");
zoo.accept(healthCheckVisitor);
}
}

7. 关键点说明

  • Animal 接口: 定义了 accept() 方法,用于接受访问者。 这就是元素 (Element) 接口.
  • Lion, Monkey, Dolphin 类: 实现了 Animal 接口,表示具体的动物种类。 每个类中的 accept() 方法调用访问者的 visit() 方法,并将自身作为参数传递。 这就是具体元素 (Concrete Element).
  • AnimalVisitor 接口: 定义了 visit() 方法,用于访问不同种类的动物。 这就是访问者 (Visitor) 接口.
  • FeedVisitor, PlayVisitor, HealthCheckVisitor 类: 实现了 AnimalVisitor 接口,定义了具体的访问行为。 每个类中的 visit() 方法实现了对特定动物的访问操作。 这就是具体访问者 (Concrete Visitor).
  • Zoo 类: 维护了一个 Animal 列表,并提供 accept 方法来接受访问者,并让访问者访问每一个动物. 这就是对象结构 (Object Structure).
  • VisitorPatternDemo 类: 客户端代码,演示了如何创建动物对象和访问者对象,以及如何使用访问者来执行不同的操作。

8. 运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--- 喂食 ---
喂狮子吃肉!
喂猴子吃香蕉!
喂海豚吃鱼!
--- 玩耍 ---
和狮子玩耍(小心点!)
狮子咆哮!
和猴子玩耍,给它挠痒痒!
猴子爬树!
和海豚玩耍,一起跳跃!
海豚游泳!
--- 健康检查 ---
检查狮子的健康状况...
检查猴子的健康状况...
检查海豚的健康状况...

9. 总结

访问者模式通过分离对象结构和操作,提供了灵活的操作扩展机制。 当对象结构稳定且需要定义多种操作时,访问者模式是一种有效的解决方案。 但需要注意的是,如果对象结构频繁变化,维护访问者类的成本也会相应增加。