目录
1、核心思想
2、实现方式
2.1 模式结构
2.2 实现案例
3、优缺点分析
4、适用场景
1、核心思想
目的:数据结构稳定的情况下,解决数据与算法的耦合问题。适用于对象结构稳定但需频繁扩展操作的场景。
实现:在访问数据时根据数据类型(重载)自动切换(双重分派)到对应的算法,实现数据的自动响应机制,并且确保算法的自由扩展。
核心:对重载方法与双派发方式的利用
-
双重分派(Double Dispatch):通过两次方法调用(先接受访问者,再调用访问者的方法)实现动态绑定。
-
解耦数据与操作:对象结构中的元素(如文档节点、抽象语法树节点)仅定义“接受访问者”的方法,具体操作由访问者实现。
举例:
1> 超市对不同商品有不同的折扣力度和计价方式
2> 文档中有不同对象(文本、图片)转换成PDF或渲染到屏幕两种输出方式
2、实现方式
2.1 模式结构
五种核心角色:
- Element(元素接口):被访问的数据元素接口,定义一个可以接待访问者的行为标准accept(Visitor visitor),且所有数据封装类需实现此接口,通常作为泛型并被包含在对象容器中。
- ConcreteElement(元素实现):具体数据元素实现类,可以有多个实现,并且相对固定。其accept实现方法中调用访问者并将自己“this”传回(如
visitor.visit(this)
)。 - Visitor(访问者接口):可以是接口或者抽象类,定义了一系列访问操作方法以处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定哪个重载方法被调用。
- ConcreteVisitor(访问者实现):访问者接口的实现类,可以有多个实现,每个访问者类都需实现所有数据元素类型的访问重载方法。
- ObjectContainer(对象容器):包含所有可被访问的数据对象(元素)的容器--元素的集合(如列表、树),提供遍历元素的方法,供访问者访问。
2.2 实现案例
假设有一个文档对象结构,包含文本和图片元素,需要支持导出为PDF和渲染到屏幕两种操作:
//1、元素接口
interface DocumentElement {
void accept(Visitor visitor);
}
//2、元素实现
// 具体元素:文本
class TextElement implements DocumentElement {
private String content;
public TextElement(String content) {
this.content = content;
}
public String getContent() { return content; }
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 调用访问者的visit(TextElement)方法
}
}
// 具体元素:图片
class ImageElement implements DocumentElement {
private String path;
public ImageElement(String path) {
this.path = path;
}
public String getPath() { return path; }
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 调用访问者的visit(ImageElement)方法
}
}
//3、访问者接口
interface Visitor {
void visit(TextElement text);
void visit(ImageElement image);
}
//4、访问者实现
// 具体访问者:导出为PDF
class ExportToPDFVisitor implements Visitor {
@Override
public void visit(TextElement text) {
System.out.println("导出文本内容到PDF: " + text.getContent());
}
@Override
public void visit(ImageElement image) {
System.out.println("导出图片到PDF: " + image.getPath());
}
}
// 具体访问者:渲染到屏幕
class RenderVisitor implements Visitor {
@Override
public void visit(TextElement text) {
System.out.println("显示文本: " + text.getContent());
}
@Override
public void visit(ImageElement image) {
System.out.println("显示图片: " + image.getPath());
}
}
//5、对象容器:文档对象
class Document {
private List<DocumentElement> elements = new ArrayList<>();
public void addElement(DocumentElement element) {
elements.add(element);
}
// 允许访问者遍历所有元素
public void accept(Visitor visitor) {
for (DocumentElement element : elements) {
element.accept(visitor);
}
}
}
//6、客户端
public class Client {
public static void main(String[] args) {
Document doc = new Document();
doc.addElement(new TextElement("Hello World"));
doc.addElement(new ImageElement("photo.jpg"));
// 导出为PDF
Visitor exportVisitor = new ExportToPDFVisitor();
doc.accept(exportVisitor);
// 输出:
// 导出文本内容到PDF: Hello World
// 导出图片到PDF: photo.jpg
// 渲染到屏幕
Visitor renderVisitor = new RenderVisitor();
doc.accept(renderVisitor);
// 输出:
// 显示文本: Hello World
// 显示图片: photo.jpg
}
}
3、优缺点分析
优点:
-
开闭原则:新增操作(访问者)无需修改元素类。
-
集中操作逻辑:将相关操作聚合到访问者中,便于维护。
-
支持复杂操作:访问者可跨多个元素类实现全局逻辑(如统计文档字数)。
缺点:
-
破坏封装性:访问者需访问元素的内部状态,可能导致元素暴露私有字段。
-
元素类扩展困难:新增元素类需修改所有访问者接口及实现。
-
复杂度高:双重分派机制理解成本较高,代码结构更复杂。
4、适用场景
-
对象结构稳定但操作多变
-
如编译器中的抽象语法树(AST)处理(类型检查、代码生成、格式化)。
-
-
跨元素类的复杂操作
-
如统计报表生成、文档格式转换。
-
-
解耦数据与操作
-
如GUI框架中,控件树支持多种事件处理逻辑。
-