设计模式-Visitor模式(访问者模式)

什么是访问者模式

将数据结构与处理分离开来。

比如一个人,他有自己的名字,他可以吃东西。这里的“人”就是一个数据结构,名字是属性,吃东西是行为方法(处理)。

public class Person {
    private String name;
    
    public void eat() {
        System.out.println("自己吃东西");
    }
}

将数据结构中的处理分离出去,就像这样换成一个accept方法,接收一个访问者,然后将自己作为参数,调用访问者的visit方法

public class Person {
    private String name;

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

在访问者中,调用相应的visit方法,让访问者“帮我吃东西”

public class Visitor {
    public void visit(Person person) {
        System.out.println("观察者帮person吃");
    }
}

另一个例子

在下面这个例子中,使用Composite模式中用到的那个文件和文件夹的例子作为访问者要访问的数据结构。

Visitor类,表示访问者的抽象类。访问者依赖于它所访问的数据结构(即File类和Directory类)

public abstract class Visitor {
    public abstract void visit(File file);
    public abstract void visit(Directory directory);
}

Element接口,只有一个accept方法,表示实现了这个接口的类,都接受访问者来访问自己(把本类的处理分离出去)。

public abstract class Element {
    public abstract void accept(Visitor visitor);
}

Entry类,与Composite模式中的Entry类基本一致,表示抽象容器,用来实现File和Directory的一致性。

public abstract class Entry extends Element{
    public abstract String getName();
    public abstract int getSize();
    public Entry add(Entry entry) throws FileTreatmentException {
        throw new FileTreatmentException();
    }

    public Iterator iterator() throws FileTreatmentException {
        throw new FileTreatmentException();
    }

    public String toString() {
        return getName() + " (" + getSize() + ")";
    }
}

File类,实现了accept方法,传入一个访问者,然后通过多态调用相应的visit方法,将this作为参数传给访问者。

public class File extends Entry {

    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 visitor) {
        visitor.visit(this);
    }
}

Directory类,也实现了accept方法,与File的做法一样。

public class Directory extends Entry {
    private String name;
    private ArrayList directory = new ArrayList();

    public Directory(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        int size = 0;
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            size += entry.getSize();
        }
        return size;
    }

    @Override
    public Entry add(Entry entry) throws FileTreatmentException {
        directory.add(entry);
        return this;
    }

    @Override
    public Iterator iterator() throws FileTreatmentException {
        return directory.iterator();
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

ListVisitor类,是Visitor的子类,表示具体的访问者。

  1. visit(File file)方法是用来实现“对File类的实例要进行的处理”,这里只是简单的输出文件夹名。
  2. visit(Directory directory)方法实现了“对Directory类的实例要进行的处理”,这里会递归调用directory中的file和其中的子文件夹,即accept方法与visit方法之间的相互递归调用
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 iterator = directory.iterator();
        while (iterator.hasNext()) {
            Entry entry = (Entry) iterator.next();
            entry.accept(this);
        }
        currentdir = saveDir;
    }
}

异常类,这个不是重点

public class FileTreatmentException extends RuntimeException {
    public FileTreatmentException() {
    }

    public FileTreatmentException(String message) {
        super(message);
    }
}

测试类

public class Main {
    public static void main(String[] args) {
        try {
            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", 10000));
            bindir.add(new File("latex", 20000));
            rootDir.accept(new ListVisitor());

        } catch (FileTreatmentException e) {
            e.printStackTrace();
        }
    }
}

双重分发

在Visitor模式中,ConcreteElement和ConcreteVisitor这两个角色共同决定了实际进行的处理。这种消息分发的方式一般被称为双重分发

开闭原则——对扩展开放,对修改关闭

开闭原则(The Open-Closed Principle,OCP),就是在不修改现有代码的前提下进行扩展。比如这里的File和Directory就具有高可复用性,可作为组件复用,如果需要改变对其的处理方式,只需要增加ConcreteVisitor类,而不用修改File和Directory(File的accpet方法不用改变)

优缺点

优点:易于增加ConcreteVisitor角色
缺点:难以增加ConcreteElement角色,如果要增加一个Device类继承了Entry类,那么它肯定也要实现accept方法,那抽象访问者肯定要加一个专门给Device类的访问方法visit(Device device),这样全部的ConcreteVisitor都要实现这个新增方法,非常麻烦。

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值