概念
将数据结构和作用于数据结构上的操作分离开来,让新的操作可以在不改变数据结构的前提下添加进来
能够不在类中新增方法,实现对类中数据的操作
应用场景
为对象增加操作方法,但不需要将操作添加到类中
需要对一类不同的对象增加相同的操作,使用访问者方法不需要再每个类中编写操作方法,将操作方法集中管理

基本结构

1)元素接口(Element):这是所有元素的基类或者接口,声明了接受访问者的accept()方法。每个元素类都会实现该方法,用来将访问者传递给自己
2)具体元素(ConcreteElement):每个具体元素类都实现了抽象元素的accept()方法,通常这个方法会将自己传递给访问者,让访问者执行特定的操作
3)访问者接口(Visitor):定义了针对每个具体元素类的操作。每个具体访问者都实现这个接口,并且为每个元素类提供不同的操作
4)具体访问者(ConcreteVisitor):实现了抽象访问者接口,并为每个具体元素提供特定的操作
代码实现
以 “文档格式转换” 为例
访问者接口
规定访问者需要实现的方法
public interface DocumentVisitor {
void visit(PDFDocument pdf);
void visit(WordDocument word);
void visit(ExcelDocument excel);
}
元素接口
声明一个accept()方法,使得每个元素能够接受访问者,参数中传入一个访问者,调用访问者实现的visit()方法
public interface Document {
void accept(DocumentVisitor visitor);
}
具体元素
每个文档类都实现 accept 方法,并在其中调用访问者的对应方法,把自身 this 传过去。这样访问者就可以获取到文档数据并处理,而文档类不需要知道“怎么处理”
public class PDFDocument implements Document {
private String content;
public PDFDocument(String content) {
this.content = content;
}
public String getContent() {
return content;
}
@Override
public void accept(DocumentVisitor visitor) {
visitor.visit(this);
}
}
public class WordDocument implements Document {
private String content;
public WordDocument(String content) {
this.content = content;
}
public String getContent() {
return content;
}
@Override
public void accept(DocumentVisitor visitor) {
visitor.visit(this);
}
}
public class ExcelDocument implements Document {
private String[][] table;
public ExcelDocument(String[][] table) {
this.table = table;
}
public String[][] getTable() {
return table;
}
@Override
public void accept(DocumentVisitor visitor) {
visitor.visit(this);
}
}
具体访问者
访问者类实现了将文档转换为 HTML 的具体逻辑。每种文档的转换方式不同,但它们共用一个访问者接口,使得扩展变得灵活又统一
public class HtmlExportVisitor implements DocumentVisitor {
@Override
public void visit(PDFDocument pdf) {
System.out.println("<html><body><h1>PDF 内容</h1><p>" + pdf.getContent() + "</p></body></html>");
}
@Override
public void visit(WordDocument word) {
System.out.println("<html><body><h1>Word 内容</h1><p>" + word.getContent() + "</p></body></html>");
}
@Override
public void visit(ExcelDocument excel) {
System.out.println("<html><body><h1>Excel 内容</h1><table border='1'>");
for (String[] row : excel.getTable()) {
System.out.print("<tr>");
for (String cell : row) {
System.out.print("<td>" + cell + "</td>");
}
System.out.println("</tr>");
}
System.out.println("</table></body></html>");
}
}
客户端调用
具体元素只需接受具体访问者即可调用访问者的方法
public class Client {
public static void main(String[] args) {
Document pdf = new PDFDocument("这是 PDF 文件的内容");
Document word = new WordDocument("这是 Word 文档的内容");
Document excel = new ExcelDocument(new String[][] {
{"姓名", "成绩"},
{"鱼皮", "90"},
{"Yes哥", "95"}
});
DocumentVisitor htmlExporter = new HtmlExportVisitor();
pdf.accept(htmlExporter);
word.accept(htmlExporter);
excel.accept(htmlExporter);
}
}
优缺点
优点
1)集中操作:将操作集中在访问者类中,避免了在各个元素类中重复实现相同的操作
2)扩展性强:访问者模式可以让我们在不改变元素类(被访问者类)的情况下,增加新的操作
缺点
1)修改元素类困难:虽然增加新操作很方便,但一旦要修改元素类的结构,就会影响到所有的访问者
