访问者模式
引入
在数据结构中保存着许多元素,我们会对这些元素进行处理。一般来说我们将处理的代码放在表示数据结构的类中。但是,如果处理有许多种呢?当每增加一种处理,我们就不得不取修改数据结构的类。
在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方法。