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