今天说说你不知道的访问者模式

85 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情

访问者模式

引入

​ 在数据结构中保存着许多元素,我们会对这些元素进行处理。一般来说我们将处理的代码放在表示数据结构的类中。但是,如果处理有许多种呢?当每增加一种处理,我们就不得不取修改数据结构的类。

​ 在Visitor模式中,**数据结构与处理被分离开来。**我们编写一个表示访问者的类来处理数据结构中的元素,并把个元素的处理交给访问者类。当增加新的处理时,我们只需要增加访问者类即可。

事例程序

类与接口

名字说明
Visitor访问者抽象类,访问文件和文件夹
Element表示数据结构的接口,接受访问者访问
ListVisitorVisitor子类,显示文件和文件夹
Entity抽象类,实现Element接口,File和Director父类
File文件
Director目录
Main测试

类图

image-20221218142328799

代码

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)

时序图:

image-20221218150225294

  • 对于Directory的实例和File的实例,我们调用他们的accept方法
  • 对于每一个Directory的实例和File的实例,我们只调用一个他们的accept方法
  • 对于ListVisitor实例,我们调用vist(Directory)和vist(File)方法
  • 处理vist(Directory)和vist(File)方法是同一个ListVisitor实例

登场角色

image-20221218150643199

  • 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方法。