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

  1. package Visitor;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/10/24
  5. * 表示访问者的抽象类,依赖它所访问的数据结构(File 类 和 Directory 类)
  6. **/
  7. public abstract class Visitor {
  8. // 访问文件的类
  9. public abstract void visit(File file);
  10. // 访问目录的类
  11. public abstract void visit(Directory director);
  12. }

Element

  1. package Visitor;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/10/24
  5. * Element 接口表示接受访问者(Visitor)的访问接口
  6. **/
  7. public interface Element {
  8. public abstract void accept(Visitor v);
  9. }

ListVisitor

  1. package Visitor;
  2. import java.util.Iterator;
  3. /**
  4. * @Author Gorit
  5. * @Date 2021/10/24
  6. * 功能:访问数据结构,并显示一览
  7. * 实现:visit(File) visit(Directory) 访问
  8. * 在 Visitor 模式中,visit 方法将处理集中在 ListVisitor
  9. **/
  10. public class ListVisitor extends Visitor {
  11. private String currentDir = ""; // 当前访问文件夹的名字
  12. @Override
  13. public void visit(File file) { // 在访问文件时调用
  14. System.out.println(currentDir + "/" + file);
  15. }
  16. /**
  17. * visit(File) 方法用来实现 “对 File 类的实例要进行的处理”
  18. * visit(Directory) 方法实现了 “对 Directory 类的实例进行的处理”
  19. * @param director
  20. */
  21. @Override
  22. public void visit(Directory director) { // 在访问文件夹时调用
  23. System.out.println(currentDir + "/" + director);
  24. String saveDir = currentDir;
  25. currentDir = currentDir + "/" + director.getName();
  26. Iterator it = director.iterator();
  27. while (it.hasNext()) {
  28. Entry entry = (Entry) it.next();
  29. entry.accept(this);
  30. }
  31. currentDir = saveDir;
  32. }
  33. }

Entry

  1. package Visitor;
  2. import java.util.Iterator;
  3. /**
  4. * @Author Gorit
  5. * @Date 2021/10/24
  6. * 本质上和 Composite 的 Entry 是一样的。
  7. * 实现 Element 接口中声明的抽象方法,为了使 Entry 适用 Visitor 模式
  8. **/
  9. public abstract class Entry implements Element{
  10. public abstract String getName(); // 获取名字
  11. public abstract int getSize(); // 获取大小
  12. public Entry add(Entry entry) { // 增加目录条目
  13. throw new FileTreatmentException();
  14. }
  15. public Iterator iterator() { // 生成 Iterator
  16. throw new FileTreatmentException();
  17. }
  18. public String toString() { // 显示字符串
  19. return getName() + " (" + getSize() +") ";
  20. }
  21. }

File

  1. package Visitor;
  2. import java.util.Iterator;
  3. /**
  4. * @Author Gorit
  5. * @Date 2021/10/24
  6. * 本质上和 Composite 的 Entry 是一样的。
  7. * 实现 Element 接口中声明的抽象方法,为了使 Entry 适用 Visitor 模式
  8. **/
  9. public abstract class Entry implements Element{
  10. public abstract String getName(); // 获取名字
  11. public abstract int getSize(); // 获取大小
  12. public Entry add(Entry entry) { // 增加目录条目
  13. throw new FileTreatmentException();
  14. }
  15. public Iterator iterator() { // 生成 Iterator
  16. throw new FileTreatmentException();
  17. }
  18. public String toString() { // 显示字符串
  19. return getName() + " (" + getSize() +") ";
  20. }
  21. }

Directory

  1. package Visitor;
  2. import java.util.ArrayList;
  3. import java.util.Iterator;
  4. /**
  5. * @Author Gorit
  6. * @Date 2021/10/24
  7. **/
  8. public class Directory extends Entry {
  9. private String name; // 文件夹名字
  10. private ArrayList dir = new ArrayList(); // 目标条目集合
  11. public Directory(String name) {
  12. this.name = name;
  13. }
  14. public String getName() { // 获取名字
  15. return name;
  16. }
  17. public int getSize() {
  18. int size = 0;
  19. Iterator it = dir.iterator();
  20. while (it.hasNext()) {
  21. Entry entry = (Entry) it.next();
  22. size += entry.getSize();
  23. }
  24. return size;
  25. }
  26. public Entry add(Entry entry) { // 增加目录条目
  27. dir.add(entry);
  28. return this;
  29. }
  30. public Iterator iterator() { // 生成 iterator
  31. return dir.iterator();
  32. }
  33. public void accept(Visitor v) { // 接受访问者的访问
  34. v.visit(this);
  35. }
  36. }

FileTreatmentException

  1. package Visitor;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/10/7
  5. **/
  6. public class FileTreatmentException extends RuntimeException {
  7. public FileTreatmentException() {
  8. }
  9. public FileTreatmentException(String msg) {
  10. super(msg);
  11. }
  12. }

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 两个角色共同决定进行的处理。这种消息分发的方式成为 双重分发

三、此示例中登场的角色

  1. Visitor(访问者)

Visitor 角色负责对数据结构中每个具体的元素(ConcreateElement 角色)声明一个用于访问 XXX 的 visit(XXX) 方法。visit(XXXX) 是用来处理 XXX 的方法,由 Visitor 扮演此角色。

  1. ConcreateVisitor(具体的访问者)

ConcreateVisitor 用来实现 Visitor 所定义的接口(API),它要实现所有 vist(XXX) 方法,本示例中由 ListVisitor 类扮演此角色。

  1. Element(元素)

Element 角色表示 Visitor 角色的访问对象。它声明了接受访问者的 accept() 方法,accept 接收的是 Visitor 对象。本示例中油 Element 扮演此角色。

  1. ConcreateElement

负责实现 Element 角色定义的接口,示例程序中由 File 和 Directory 类表示

  1. ObjectStructure(对象结构)

负责处理 Element 角色的集合。ConcreateVisitor 角色为每个 Element 角色都准备了处理方法。在示例程序中,由 Directory 类扮演此角色)一人分饰两角。为了让 ConcreateVisitor 角色可以遍历每个 Element 角色,在示例程序中,我们在 Directory 类中实现了 Iterator 方法