访问者模式
访问者
应用场景 (实际问题需求) 与 传统实现方式
例如:
完成一个测评系统:
将观众分为男人和女人, 对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价分不同种类,例如: 成功 失败 等)
传统实现:将评价操作直接定义在男人,女人类中。
传统实现的问题:
1. 不利于维护。
2. 扩展性不好,增加 新的人员类型 , 或者管理方法,导致改动大。
引入访问者设计模式
1. 定义
在访问者模式中, 使用一个访问者接口, 通过定义不同的访问者接口实现类,可以改变被访问者的执行操作,通过这种方式,被访问者的执行算法可以随着访问者改变而改变。此时访问者相当于被访问者可以执行(展示)出的不同种类的操作。
根据模式,被访问对象接收访问者对象(accept方法中参数是访问者对象),通过回调访问者对象中的不同方法,被访问者就可以展示出不同的操作。
这种类型的设计模式属于行为型模式。
2. 意图
主要是将数据结构和数据操作分离。
解决数据结构和操作耦合性问题。
它使你可以在不改变 被访问者类的前提下 扩展被访问者类的不同的操作。
问题原理类图

模式原理剖析 (主要角色及职责)
模式中存在的角色:
visitor: 抽象访问者,在案例中为【测评】操作,其中【测评】操作应该为Person(Element)对象结构中的 男人和女人(ConcreteElement)的每一个具体类声明一个visit操作。
ConcreteVisitor: 是具体的访问者类,在案例中是success 和 fail 评价,实现由Action(visitor)声明的操作,是【测评】操作实现的部分,通过传入不同的实例化对象调用不同的方法。
ObjectStructure: 能枚举Element的元素, 案例中就是ObjectStructure类,声明Person类型集合,ObjectStructure用于提供一个高层的接口,用来允许访问者访问元素。
Element : 元素类(被访问者),案例中是Person类,内部定义一个accept方法, 接收一个访问者对象。
ConcreteElement: 继承或实现Element,是具体元素,具体的被访问者,案例中是男人和女人,实现 accept方法(调用操作),在 accept 方法中,将测评visitor接口作为参数,调用【评测】操作,通过多态调用(编译看左,运行看右)。
分析实现步骤
先确定 访问者(ConcreteVisitor) 和 被访问者(ConcreteElement) 是哪些:
1. 定义上层Visitor接口 抽象访问者,添加对ConcreteElement的每一个类不同的visit操作。
2. 定义Element 接口,定义 accept方法 表示接受访问者访问。
3. 定义不同的ConcreteVisitor类,每个类代表了不同的操作,实现Visitor 实现 visit 操作。
4. 定义不同的ConcreteElement类,每个类代表了不同的被访问者,操作调用者(接收访问)。实现Element的accept方法。
5. 定义ObjectStructure类:用于管理访问者(ConcreteVisitors)去访问被访问者(ConcreteElements),通常将被访问者放入集合中使用。
6. 定义Client类 用于访问ObjectStructure 进行测试。
代码实现
// 访问者 : 失败 和 成功(测评操作) , 被访问者 : 男人 女人 (观众)
// 定义 Visitor 抽象类 / 接口
public abstract class Action {
// 对于不同被访问者 执行 不同操作
abstract void getResult(Man man);
abstract void getResult(Woman woman);
}
// 定义 Element 抽象类 / 接口
public abstract class Person {
// 表示接受访问
abstract void accept(Action action);
}
// 定义 不同的ConcreteVisitor , 不同的ConcreteVisitor定义对于不同的ConcreteElements进行不同的操作
// 定义 success时的操作
public class Success extends Action{
// success时,对男人的操作
@Override
void getResult(Man man) {
System.out.println("man say success");
}
// success时,对女人的操作
@Override
void getResult(Woman woman) {
System.out.println("woman say success");
}
}
// 定义 Fail时的操作
public class Fail extends Action{
// Fail时,对男人的操作
@Override
void getResult(Man man) {
System.out.println("man say fail");
}
// Fail时,对女人的操作
@Override
void getResult(Woman woman) {
System.out.println("woman say fail");
}
}
// 定义被访问者 , 实现 accept 操作
// 定义 man, 实现 accept
public class Man extends Person{
// 表示接受访问者(Action), 通过访问者调用 需要执行的操作
@Override
void accept(Action action) {
action.getResult(this); // 传入 this , 通过本身数据类型,调用action中不同的getResult重载方法。
}
}
// 定义woman,实现 accept
public class Woman extends Person{
// 表示接受访问者(Action), 通过访问者调用 需要执行的操作
@Override
void accept(Action action) {
action.getResult(this);
}
}
// 定义ObjectStructure , 从高层进行访问者访问操作
public class ObjectStructure {
/**
* 维护了一个集合
*/
private List<Person> list = new ArrayList<>();
// 向集合中添加被访问者元素
public void attach(Person p) {
list.add(p);
}
// 从集合中移除被访问者元素
public void detach(Person p) {
list.remove(p);
}
// action 访问 被访问者 , 在 Client 中传入不同的访问者 , 执行不同操作
public void display(Action action) {
for (Person person : list) {
person.accept(action);
}
}
}
// Client 调用测试
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
Success success = new Success();
objectStructure.display(success);
// 直接添加一个 Wait(评价待定) 类 , 就可以进行不同的操作。
Wait wait = new Wait();
objectStructure.display(wait);
}
}
// 添加Wait类
public class Wait extends Action{
/**
* man的 wait评价 方法
* @param man 传入 Person子类 man
*/
@Override
void getResult(Man man) {
System.out.println("man say wait");
}
/**
* woman的 wait评价方法
* @param woman 传入Person子类 woman
*/
@Override
void getResult(Woman woman) {
System.out.println("woman say wait");
}
}
难点解析
Action(操作接口) 和 Person(元素接口) 之间的联系:
- Action接口中定义 getResult 操作,重载方法定义不同getResult 抽象方法,参数使用不同的具体的Person实现类,在实现类中实现多个getResult方法,传入参数为Person变量,根据不同的实现类实例对象,调用不同参数。
- Person接口定义 accept 方法,表示进行 评价 操作,在实现类 man和woman 中 实现 accept方法,调用action的getResult方法,其参数是 Action 变量,根据不同的Action实现类实例化对象,调用实例化对象的getResult,根据传入的this对象,选择不同的实现方式。
总结
访问者模式主要应用场景:需要对一个(Elements)对象结构中的对象进行很多不同类型操作(这些操作彼此没有关联),同时需要避免让 这些频繁修改增添的操作 直接写在这些类中,可以使用访问者模式。
优点:
- 符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则, 另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展。
- 扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展。
练习:
使用访问者模式 展示 一个开启电脑的操作,使电脑的各个部分均输出display
其中电脑分成 主机 键盘 鼠标 显示器,
UML:
步骤 1
定义一个表示元素的接口。
ComputerPart.java
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}
步骤 2
创建扩展了上述类的实体类。
Keyboard.java
public class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Monitor.java
public class Monitor implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Mouse.java
public class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
Computer.java
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);
}
}
步骤 3
定义一个表示访问者的接口。
ComputerPartVisitor.java
public interface ComputerPartVisitor {
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}
步骤 4
创建实现了上述类的实体访问者。
ComputerPartDisplayVisitor.java
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.");
}
}
步骤 5
使用 ComputerPartDisplayVisitor 来显示 Computer 的组成部分。
VisitorPatternDemo.java
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}
/*
步骤 6
执行程序,输出结果:
Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)