访问者模式 (Visitor) 全面讲解
学习笔记
设计模式
访问者模式
面向对象
Java
软件开发
编程模式
行为型设计模式
本文全面讲解了访问者模式,包括其定义、核心思想、适用场景、与传统方式的对比以及模式结构。通过实例和类比,深入浅出地介绍了如何使用访问者模式来分离算法与对象结构,提高代码的可扩展性和可维护性。
date: 2025-06-22T22:24:00 tags:
- 复习
- 面向对象
- 设计模式
- Java
访问者模式 (Visitor) 全面讲解
1. 什么是访问者模式?
访问者模式是行为型设计模式,用于将算法与对象结构分离,使得在不修改对象结构的前提下,能为对象结构中的元素添加新操作。
- 核心思想:通过**双重分发(Double Dispatch)**机制,让操作可以独立变化。
- 类比:
- 医生(Visitor)为不同病人(Element)开具不同药方(visit方法)。
- 文件系统遍历(对文件和文件夹执行不同操作)。
🔹 适用场景
- 需要对一个复杂对象结构(如AST、UI组件树)执行多种不相关的操作。
- 对象结构稳定,但经常需要新增操作(如编译器中的语法分析、优化、代码生成)。
2. 为什么需要访问者模式?
问题场景(传统方式的缺陷)
假设有一个文档对象模型(DOM),包含TextNode和ElementNode:
interface Node {
// 传统方式:每个操作需修改Node接口
void highlight();
void extractText();
}
class TextNode implements Node { /*...*/ }
class ElementNode implements Node { /*...*/ }
问题:每新增一个操作(如compress()),需修改所有Node实现类,违反开闭原则!
解决方案
将操作抽取为独立的访问者,通过accept()方法动态绑定具体操作。
3. 访问者模式的结构
角色说明
Visitor(访问者接口):声明访问各类元素的visit方法(如NodeVisitor)。ConcreteVisitor(具体访问者):实现具体操作(如HighlightVisitor)。Element(元素接口):定义accept(Visitor)方法(如Node)。ConcreteElement(具体元素):实现accept方法(如TextNode)。ObjectStructure(对象结构):维护元素集合(如文档树)。
4. 示例代码(文档处理系统)
步骤1:定义元素接口
interface Node {
void accept(NodeVisitor visitor); // 关键:接收访问者
}
步骤2:实现具体元素
// 文本节点
class TextNode implements Node {
private String content;
public TextNode(String content) {
this.content = content;
}
@Override
public void accept(NodeVisitor visitor) {
visitor.visit(this); // 调用访问者的visit(TextNode)方法
}
public String getContent() {
return content;
}
}
// 元素节点
class ElementNode implements Node {
private String tagName;
public ElementNode(String tagName) {
this.tagName = tagName;
}
@Override
public void accept(NodeVisitor visitor) {
visitor.visit(this); // 调用访问者的visit(ElementNode)方法
}
public String getTagName() {
return tagName;
}
}
步骤3:定义访问者接口
interface NodeVisitor {
void visit(TextNode node); // 重载方法
void visit(ElementNode node);
}
步骤4:实现具体访问者
// 高亮访问者
class HighlightVisitor implements NodeVisitor {
@Override
public void visit(TextNode node) {
System.out.println("高亮文本: " + node.getContent());
}
@Override
public void visit(ElementNode node) {
System.out.println("高亮元素: <" + node.getTagName() + ">");
}
}
// 文本提取访问者(新增操作无需修改Node类)
class TextExtractVisitor implements NodeVisitor {
@Override
public void visit(TextNode node) {
System.out.println("提取文本: " + node.getContent());
}
@Override
public void visit(ElementNode node) {
System.out.println("跳过元素: <" + node.getTagName() + ">");
}
}
步骤5:客户端调用
public class Client {
public static void main(String[] args) {
// 构建对象结构
List<Node> nodes = Arrays.asList(
new TextNode("Hello"),
new ElementNode("div")
);
// 执行高亮操作
NodeVisitor highlighter = new HighlightVisitor();
for (Node node : nodes) {
node.accept(highlighter);
}
// 执行文本提取操作
NodeVisitor extractor = new TextExtractVisitor();
for (Node node : nodes) {
node.accept(extractor);
}
}
}
输出:
高亮文本: Hello
高亮元素: <div>
提取文本: Hello
跳过元素: <div>
5. 关键点总结
🔹 双重分发(Double Dispatch)
- 第一次分发:元素调用
accept(Visitor)。 - 第二次分发:访问者调用
visit(ConcreteElement)。
🔹 优点
- 符合开闭原则:新增操作只需添加访问者,无需修改元素类。
- 集中相关操作:将分散的操作聚合到访问者中。
🔹 缺点
- 元素接口需稳定(新增元素类型需修改所有访问者)。
6. 互动实践
问题1:以下代码缺少什么关键部分?
interface Animal { /*...*/ }
class Dog implements Animal { /*...*/ }
class FeedingVisitor {
void feed(Dog dog) { /*...*/ }
}
答案:缺少Animal的accept(Visitor)方法和Visitor接口定义。
问题2:如何用访问者模式实现购物车折扣计算?
任务:
- 元素:
Book(价格100)、Fruit(价格50,重量2kg)。 - 访问者:
DiscountVisitor(书打9折,水果按重量计算)。 - 补全代码:
class DiscountVisitor implements ItemVisitor {
@Override
public void visit(Book book) { /* 计算书折扣 */ }
// 补全visit(Fruit)方法
}
7. 考试练习题
题目1(概念题)
访问者模式的核心机制是?
A. 单分派
B. 双重分发
C. 动态代理
D. 反射
答案:B
题目2(代码填空)
补全访问者模式的代码:
interface ShapeVisitor {
void visit(Circle circle);
void visit(Rectangle ____); // 填空1
}
class Circle implements Shape {
@Override
public void accept(ShapeVisitor visitor) {
____.visit(this); // 填空2
}
}
答案:
rectanglevisitor
题目3(设计题)
用访问者模式设计编译器语法树(AST)处理:
- 元素:
VariableNode、FunctionCallNode。 - 访问者:
TypeCheckVisitor、CodeGenVisitor。
要求:画出类图并写出关键代码。