Author:Gorit
Date:2021年10月24日
Refer:《图解设计模式》
13.1 Vistor 模式
数据结构中保存着许多元素,我们需要对这些元素进行“处理”。这时,“处理”代码在哪比较好呢?通常做法是将其放在表示数据结构的类当中,但是“处理”有很多种呢?这样的话,每次增加一种,我们就不得不去修改 表示数据结构的类。
在 Visitor 模式中,数据结构与处理分离开来,我们编写一个“访问者”的类来访问数据结构中的元素,并把各元素的处理交给访问者类。
这样,当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接收访问者的访问即可
13.2 示例程序
在本示例中,我们采用 Composite 模式中用到的那个文件 和 文件夹的例子作为访问者要访问的数据结构。访问者会访问由文件和文件夹构成的数据结构,然后显示文件 和 文件夹一览。
类名 | 备注 |
---|---|
Visitor | 表示访问者的抽象类,它访问文件和文件夹 |
Element | 表示数据结构的接口,它接收访问者的访问 |
ListVisitor | Visitor 类的子类,显示文件 和 文件夹一览 |
Entry | File 类 和 Directory 类的父类,它是抽象类,实现了 Element 接口 |
File | 表示文件的类 |
Directory | 表示文件夹的类 |
FileTreatementException | 表示向文件中 add 时发生异常的类 |
Main | 测试程序行为的类 |
Visitor
package Visitor;
/**
* @Author Gorit
* @Date 2021/10/24
* 表示访问者的抽象类,依赖它所访问的数据结构(File 类 和 Directory 类)
**/
public abstract class Visitor {
// 访问文件的类
public abstract void visit(File file);
// 访问目录的类
public abstract void visit(Directory director);
}
Element
package Visitor;
/**
* @Author Gorit
* @Date 2021/10/24
* Element 接口表示接受访问者(Visitor)的访问接口
**/
public interface Element {
public abstract void accept(Visitor v);
}
ListVisitor
package Visitor;
import java.util.Iterator;
/**
* @Author Gorit
* @Date 2021/10/24
* 功能:访问数据结构,并显示一览
* 实现:visit(File) visit(Directory) 访问
* 在 Visitor 模式中,visit 方法将处理集中在 ListVisitor
**/
public class ListVisitor extends Visitor {
private String currentDir = ""; // 当前访问文件夹的名字
@Override
public void visit(File file) { // 在访问文件时调用
System.out.println(currentDir + "/" + file);
}
/**
* visit(File) 方法用来实现 “对 File 类的实例要进行的处理”
* visit(Directory) 方法实现了 “对 Directory 类的实例进行的处理”
* @param director
*/
@Override
public void visit(Directory director) { // 在访问文件夹时调用
System.out.println(currentDir + "/" + director);
String saveDir = currentDir;
currentDir = currentDir + "/" + director.getName();
Iterator it = director.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
entry.accept(this);
}
currentDir = saveDir;
}
}
Entry
package Visitor;
import java.util.Iterator;
/**
* @Author Gorit
* @Date 2021/10/24
* 本质上和 Composite 的 Entry 是一样的。
* 实现 Element 接口中声明的抽象方法,为了使 Entry 适用 Visitor 模式
**/
public abstract class Entry implements Element{
public abstract String getName(); // 获取名字
public abstract int getSize(); // 获取大小
public Entry add(Entry entry) { // 增加目录条目
throw new FileTreatmentException();
}
public Iterator iterator() { // 生成 Iterator
throw new FileTreatmentException();
}
public String toString() { // 显示字符串
return getName() + " (" + getSize() +") ";
}
}
File
package Visitor;
import java.util.Iterator;
/**
* @Author Gorit
* @Date 2021/10/24
* 本质上和 Composite 的 Entry 是一样的。
* 实现 Element 接口中声明的抽象方法,为了使 Entry 适用 Visitor 模式
**/
public abstract class Entry implements Element{
public abstract String getName(); // 获取名字
public abstract int getSize(); // 获取大小
public Entry add(Entry entry) { // 增加目录条目
throw new FileTreatmentException();
}
public Iterator iterator() { // 生成 Iterator
throw new FileTreatmentException();
}
public String toString() { // 显示字符串
return getName() + " (" + getSize() +") ";
}
}
Directory
package Visitor;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @Author Gorit
* @Date 2021/10/24
**/
public class Directory extends Entry {
private String name; // 文件夹名字
private ArrayList dir = new ArrayList(); // 目标条目集合
public Directory(String name) {
this.name = name;
}
public String getName() { // 获取名字
return name;
}
public int getSize() {
int size = 0;
Iterator it = dir.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) { // 增加目录条目
dir.add(entry);
return this;
}
public Iterator iterator() { // 生成 iterator
return dir.iterator();
}
public void accept(Visitor v) { // 接受访问者的访问
v.visit(this);
}
}
FileTreatmentException
package Visitor;
/**
* @Author Gorit
* @Date 2021/10/7
**/
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String msg) {
super(msg);
}
}
Main
package Visitor;
/**
* @Author Gorit
* @Date 2021/10/24
**/
public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries");
Directory rootDir = new Directory("root");
Directory bindDir = new Directory("bin");
Directory tmpDir = new Directory("tmp");
Directory usrDir = new Directory("usr");
rootDir.add(bindDir);
rootDir.add(tmpDir);
rootDir.add(usrDir);
bindDir.add(new File("vi", 10000));
bindDir.add(new File("latex", 20000));
rootDir.accept(new ListVisitor());
System.out.println("");
System.out.println("Making user entries...");
Directory yuki = new Directory("yuki");
Directory hanok = new Directory("hanok");
Directory tomura = new Directory("tomura");
usrDir.add(yuki);
usrDir.add(hanok);
usrDir.add(tomura);
yuki.add(new File("diary.html", 5000));
yuki.add(new File("Visitor.java", 1024));
hanok.add(new File("emo.jpg", 256));
hanok.add(new File("game.doc", 500));
tomura.add(new File("yuku.mail", 300));
rootDir.accept(new ListVisitor());
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
最终结果
Making root entries
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)
Making user entries...
/root (37080)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (7080)
/root/usr/yuki (6024)
/root/usr/yuki/diary.html (5000)
/root/usr/yuki/Visitor.java (1024)
/root/usr/hanok (756)
/root/usr/hanok/emo.jpg (256)
/root/usr/hanok/game.doc (500)
/root/usr/tomura (300)
/root/usr/tomura/yuku.mail (300)
Process finished with exit code 0
13.3 扩展
一、核心处理
在 Visitor 模式中,visit 方法将“处理”都集中在 ListVisitor 里面了
visitor 模式着重强调,将处理从数据结构中分离出来
二、双重分发
accept() 调用如下:
element.accept(visitor);
visit() 方法调用如下:
visitor.visit(element);
分析不难发现,两者是相反的关系,ConcreateElement 和 ConcreateVisitor 两个角色共同决定进行的处理。这种消息分发的方式成为 双重分发
三、此示例中登场的角色
- Visitor(访问者)
Visitor 角色负责对数据结构中每个具体的元素(ConcreateElement 角色)声明一个用于访问 XXX 的 visit(XXX) 方法。visit(XXXX) 是用来处理 XXX 的方法,由 Visitor 扮演此角色。
- ConcreateVisitor(具体的访问者)
ConcreateVisitor 用来实现 Visitor 所定义的接口(API),它要实现所有 vist(XXX) 方法,本示例中由 ListVisitor 类扮演此角色。
- Element(元素)
Element 角色表示 Visitor 角色的访问对象。它声明了接受访问者的 accept() 方法,accept 接收的是 Visitor 对象。本示例中油 Element 扮演此角色。
- ConcreateElement
负责实现 Element 角色定义的接口,示例程序中由 File 和 Directory 类表示
- ObjectStructure(对象结构)
负责处理 Element 角色的集合。ConcreateVisitor 角色为每个 Element 角色都准备了处理方法。在示例程序中,由 Directory 类扮演此角色)一人分饰两角。为了让 ConcreateVisitor 角色可以遍历每个 Element 角色,在示例程序中,我们在 Directory 类中实现了 Iterator 方法