开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情
访问者模式
引入
在数据结构中保存着许多元素,我们会对这些元素进行处理。一般来说我们将处理的代码放在表示数据结构的类中。但是,如果处理有许多种呢?当每增加一种处理,我们就不得不取修改数据结构的类。
在Visitor模式中,**数据结构与处理被分离开来。**我们编写一个表示访问者的类来处理数据结构中的元素,并把个元素的处理交给访问者类。当增加新的处理时,我们只需要增加访问者类即可。
事例程序
类与接口
| 名字 | 说明 |
|---|---|
| Visitor | 访问者抽象类,访问文件和文件夹 |
| Element | 表示数据结构的接口,接受访问者访问 |
| ListVisitor | Visitor子类,显示文件和文件夹 |
| Entity | 抽象类,实现Element接口,File和Director父类 |
| File | 文件 |
| Director | 目录 |
| Main | 测试 |
类图
代码
Vivitor
- 抽象访问者,访问者依赖于它所访问的数据结构
- visit(File file) 访问文件
- visit(Directory directory)访问目录
- 重载方法,当从外部调用方法时,根据接收的参数自动选择执行相应的方法
public abstract class Visitor {
public abstract void visit(File file);
public abstract void visit(Directory directory);
}
Element
- 接收访问者的接口
- 声明accept方法,参数是Visitor
public interface Element {
public abstract void accept(Visitor v);
}
Entity
- 借用于组合模式
- 实现Element接口,为了让Entity适用于Visitor模式。具体实现交由子类
public abstract class Entity implements Element {
public abstract String getName(); //获取名字
public abstract int getSize(); //获取大小
public Entity add(Entity entity) throws Exception {
throw new RuntimeException("运行异常"); //加入目录条目
}
public Iterator iterator() throws Exception {
throw new RuntimeException();
}
@Override
public String toString() { //显示代表类文字
return getName() + "(" + getSize() + ")";
}
}
File
- 组合模式中的File
- 实现accept方法,使用 v.visit(this);此处的类型时File,所以调用的时visit(File)
public class File extends Entity {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
Director
- iterator(),获取迭代器,方便遍历文件夹中所有的文件和文件夹
- accept方法。此处调用的时v.visit(Director);(重载)
public class Directory extends Entity {
private String name;
private ArrayList<Entity> directory = new ArrayList();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
int size = 0;
Iterator<Entity> it = directory.iterator();
while (it.hasNext()) {
Entity entity = it.next();
size += entity.getSize();
}
return size;
}
@Override
public Entity add(Entity entity) throws Exception {
directory.add(entity);
return this;
}
public Iterator iterator() {
return directory.iterator();
}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
ListVisitor
- Visitor的子类,它的作用是访问数据结构并显示元素一览
- currentDir保存的是当前正在访问的文件夹名。
- visit(File file)会被File类中的accept调用,参file是File的实例。也就是说,visit(File file)是用来实现对File类的实例要进行的处理。
- 先显示当前文件夹的名字,然后获取iterator,通过iterator遍历文件夹中所有的目录条目并调用各自的accept方法。
- accept方法调用visit方法,visit方法又调用accept方法。形成递归调用
public class ListVisitor extends Visitor {
private String currentDir = ""; //当前访问文件夹的名字
@Override
public void visit(File file) {
System.out.println(currentDir + "/" + file);
}
@Override
public void visit(Directory directory) {
System.out.println(currentDir + "/" + directory);
String saveDir = currentDir;
currentDir = currentDir + "/" + directory.getName();
Iterator it = directory.iterator();
while (it.hasNext()) {
Entity entity = (Entity) it.next();
entity.accept(this);
}
currentDir = saveDir;
}
}
Main
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("making root entries...");
Directory rootDir = new Directory("root");
Directory binDir = new Directory("bin");
Directory tmpDir = new Directory("tmp");
Directory usrDir = new Directory("usr");
rootDir.add(binDir);
rootDir.add(tmpDir);
rootDir.add(usrDir);
binDir.add(new File("vi", 100));
binDir.add(new File("latex", 200));
rootDir.accept(new ListVisitor());
System.out.println();
System.out.println("making user entries...");
Directory yuki = new Directory("yuki");
Directory hahako = new Directory("hahako");
Directory tomura = new Directory("tomura");
usrDir.add(hahako);
usrDir.add(tomura);
usrDir.add(yuki);
yuki.add(new File("ddd.html", 100));
yuki.add(new File("composite.java", 200));
hahako.add(new File("memo.java", 300));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
rootDir.accept(new ListVisitor());
}
}
结果:
making root entries...
/root(300)
/root/bin(300)
/root/bin/vi(100)
/root/bin/latex(200)
/root/tmp(0)
/root/usr(0)
making user entries...
/root(1800)
/root/bin(300)
/root/bin/vi(100)
/root/bin/latex(200)
/root/tmp(0)
/root/usr(1500)
/root/usr/hahako(300)
/root/usr/hahako/memo.java(300)
/root/usr/tomura(900)
/root/usr/tomura/game.doc(400)
/root/usr/tomura/junk.mail(500)
/root/usr/yuki(300)
/root/usr/yuki/ddd.html(100)
/root/usr/yuki/composite.java(200)
时序图:
- 对于Directory的实例和File的实例,我们调用他们的accept方法
- 对于每一个Directory的实例和File的实例,我们只调用一个他们的accept方法
- 对于ListVisitor实例,我们调用vist(Directory)和vist(File)方法
- 处理vist(Directory)和vist(File)方法是同一个ListVisitor实例
登场角色
-
Visitor
负责对数据结构中每个具体的元素声明同一个用于访问的visit方法。负责实现的是具体的访问者。(Visitor)
-
ConcreteVisitor
具体的访问者,负责实现Visitor 所定义的接口。实现如何处理每个ConcreteElement角色。(ListVisitor)
-
Element
表示Visitor角色的访问对象。声明接受访问者的accept方法,接受visitor参数。(Element)
-
ConcreteElement
实现Element所定义的接口。(File和Directory)、
-
ObjectStructure(对象结构)(Directory)
负责处理Element角色的集合。ConcreteVisitor为每个Element都准备好了处理方法。为了让ConcreteVisitor可以遍历处理到每个Element角色,在Directory中实现了iterator方法。