设计模式:Visitor模式

28 篇文章 2 订阅
26 篇文章 5 订阅

Visitor模式——访问数据结构并处理数据

在数据结构中保存着许多元素,我们会对这些元素进行“处理”。这时,如通常的做法是把“处理”的代码放到类中,但是当“处理”的方式有很多种时,这时,每增加一种处理,就需要去修改表示数据结构的类。

在Visitor模式中,**数据结构与处理被分离开来。我们编写一个表示“访问者”的类来访问数据结构中的数据,并把对各元素的处理交给访问者类。**这样,当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接受访问者的访问即可。

下面的示例程序使用到了Composite模式中文件和文件夹的递归的数据结构,并编写了两个访问者,一个访问者可以访问由文件和文件夹构成的数据结构,然后显示出文件和文件夹的一览;另一个访问者可以访问由文件和文件夹构成的数据结构,然后过滤出带有指定后缀名的文件。关于Composite模式中文件和文件夹的递归的数据结构,参见链接(设计模式:Composite模式)。

  • 类和接口的一览表
名字说明
Visitor表示访问者的抽象类,它访问文件和文件夹
Element表示数据结构的接口类,它接受访问者的访问
EntryFile类和Directory类的父类,它是抽象类(实现了Element接口)
File表示文件的类
Directory表示文件夹的类
FileTreatmentException表示对文件调用文件夹的方法时发生异常的类
ListVisitorVisitor类的子类,显示文件和文件夹一览
FileFindVisitorVisitor类的子类,过滤带有指定后缀名的文件
Main测试程序行为的类
  • Visitor类
public abstract class Visitor {
    public abstract void visit(File file);

    public abstract void visit(Directory directory);
}

  • Element接口
public interface Element {
    public abstract void accept(Visitor v);
}

  • Entry类
import java.util.Iterator;

public abstract class Entry implements 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();
    }

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

  • File类
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 v) {
        v.visit(this);
    }
}

  • Directory类
import java.util.ArrayList;
import java.util.Iterator;

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

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

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

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

    @Override
    public Entry add(Entry entry) {
        dir.add(entry);
        return this;
    }

    @Override
    public Iterator iterator() {
        return dir.iterator();
    }

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

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

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

  • ListVisitor类
import java.util.Iterator;

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()) {
            Entry entry = (Entry)it.next();
            entry.accept(this);
        }
        currentdir = savedir;
    }
}

  • FileFindVisitor类
import java.util.ArrayList;
import java.util.Iterator;

public class FileFindVisitor extends Visitor {
    private String filetype;
    private ArrayList found = new ArrayList();

    public FileFindVisitor(String filetype) {
        this.filetype = filetype;
    }

    public Iterator getFoundFiles() {
        return found.iterator();
    }

    @Override
    public void visit(File file) {
        if (file.getName().endsWith(filetype)) {
            found.add(file);
        }
    }

    @Override
    public void visit(Directory directory) {
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            entry.accept(this);
        }
    }
}

  • Main类
import java.util.Iterator;

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 userdir = new Directory("usr");
            rootdir.add(bindir);
            rootdir.add(tmpdir);
            rootdir.add(userdir);
            bindir.add(new File("vi", 10000));
            bindir.add(new File("latex", 20000));
            rootdir.accept(new ListVisitor());

            System.out.println("");
            System.out.println("Making user entries...");
            Directory yuki = new Directory("yuki");
            Directory hanako = new Directory("hanako");
            Directory tomura = new Directory("tomura");
            userdir.add(yuki);
            userdir.add(hanako);
            userdir.add(tomura);
            yuki.add(new File("diary.html", 100));
            yuki.add(new File("Composite.java", 200));
            hanako.add(new File("memo.tex", 300));
            hanako.add(new File("index.html", 350));
            tomura.add(new File("game.doc", 400));
            tomura.add(new File("junk.mail", 500));
            rootdir.accept(new ListVisitor());

            System.out.println("");
            System.out.println("Finding HTML entries...");
            FileFindVisitor ffv = new FileFindVisitor(".html");
            rootdir.accept(ffv);
            System.out.println("HTML files are:");
            Iterator it = ffv.getFoundFiles();
            while (it.hasNext()) {
                File file = (File)it.next();
                System.out.println(file);
            }
        } 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(31850)
/root/bin(30000)
/root/bin/vi(10000)
/root/bin/latex(20000)
/root/tmp(0)
/root/usr(1850)
/root/usr/yuki(300)
/root/usr/yuki/diary.html(100)
/root/usr/yuki/Composite.java(200)
/root/usr/hanako(650)
/root/usr/hanako/memo.tex(300)
/root/usr/hanako/index.html(350)
/root/usr/tomura(900)
/root/usr/tomura/game.doc(400)
/root/usr/tomura/junk.mail(500)

Finding HTML entries...
HTML files are:
diary.html(100)
index.html(350)


##Visitor模式中的角色

  • Visitor(访问者)

Visitor角色负责对数据结构中每个具体的元素(ConcreteElement角色)声明一个用于访问XXXXX的visit(XXXXX)方法。visit(XXXXX)是用于处理XXXXX的方法,负责实现该方法的是ConcreteVisitor角色。在示例程序中,由Visitor类扮演此角色。

  • ConcreteVisitor(具体的访问者)

ConcreteVisitor角色负责实现Visitor角色所定义的接口(API)。它要实现所有的visit(XXXXX)方法,即实现如何处理每个ConcreteElement角色。在示例程序中,由ListVisitor类和FileFindVisitor类扮演此角色。如同在ListVisitor中,currentdir字段的值在不断变化一样,随着visit(XXXXX)处理的进行,ConcreteVisitor角色的内部状态也会不断地发生变化。

  • Element(元素)

Element角色表示Visitor角色的访问对象。它声明了接受访问者的accept方法。accept方法接收到的参数是Visitor角色。在示例程序中,由Element接口扮演此角色。

  • ConcreteElement(具体的元素)

ConcreteElement角色负责实现Element角色所定义的接口(API)。在示例程序中,由File类和Directory类扮演此角色。

  • ObjectStructure(对象结构)

ObjectStructure角色负责处理Element角色的集合。ConcreteVisitor角色为每个Element角色都准备了处理方法。在示例程序中,由Directory类扮演此角色。为了让ConcreteVisitor角色可以遍历处理每个Element角色,在示例程序中,Directory类中实现了iterator方法。


##Visitor模式的思路

  • 双重分发

在Visitor模式中,调用关系如下。

element.accept(visitor);
visitor.visit(element);

可以看出,这两个方法是相反的关系。element接受visitor,而visitor又访问element。

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

  • 将处理从数据结构中分离出来

Visitor模式的目的是将处理从数据结构中分离出来。数据结构能将元素集合和元素关联在一起,但是,保存数据结构与以数据结构为基础进行处理是两种不同的操作。

ConcreteVisitor角色的开发可以独立于ConcreteElement角色,也就是说,Visitor模式提高了
ConcreteElement角色作为组件的独立性

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

  • 对扩展(extension)是开放(open)的

  • 对修改(modification)是关闭(close)的

在设计类时,必须要考虑到将来可能会扩展类。绝不能毫无理由地禁止扩展类。所以“对扩展是开放的”。

但是如果在每次扩展类时都需要修改现有的类就太麻烦了。所以我们需要在不用修改现有类的前提下能够扩展类,所以“对修改是关闭的”。

我们提倡在不修改现有代码的前提下进行扩展,这就是开闭原则。

  • Visitor工作的条件

在Visitor模式中,访问者只有从数据结构中获取了足够多的信息后才能工作。这样做的缺点是,如果公开了不应当被公开的信息,将来对数据结构的改良就会变得非常困难。


想了解更多关于设计模式:设计模式专栏)
设计模式总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值