【笔记整理】图解设计模式 | 第13章 Visitor模式(访问数据结构并处理数据)

【笔记整理】图解设计模式 | 导航


定义

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

Visitor模式中登场角色

  • Visitor(访问者)

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

  • ConcreteVisitor(具体的访问者)

       ConcreteVisitor角色负责实现Visitor角色所定义的接口(API)。它要实现所有的visit(XXXXX)方法,即实现如何处理每个ConcreteElement角色。

  • Element(元素)

       Element角色表示Visitor角色的访问对象。它声明了接受访问者的accept方法。accept方法接收到的参数是Visitor角色。

  • ConcreteElement

       ConcreteElement角色负责实现Element角色所定义的接口(API)。

  • ObjectStructure(对象结构)

       ObjectStructure角色负责处理Element角色的集合。ConcreteVisitor角色为每个Element角色都准备了处理方法。


Visitor模式的类图


拓展思路的要点

  • 双重分发

       整理一下Visitor模式中方法的调用关系:

       accept(接受)方法的调用方式如下:

       element.accept(visitor);

       而visit(访问)方法的调用凡是如下:

       visitor.visit(element);

       对比一下这两个方法会发现,它们是相反的关系。element接受visitor,而visitor又访问element。

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

  • 为什么要弄得这么复杂

       Visitor模式的目的是将处理从数据结构中分离出来。数据结构很重要,它能将元素集合和关联在一起。但是,需要注意的是,【保存数据结构】与【以数据结构为基础进行处理】是两种不同的东西。

       Visitor模式提高了ConcreteElement角色作为组件的独立性。如果将进行处理的方法定义在ConcreteElement角色中,当每次要扩展功能,增加新的“处理”时,就不得不去修改ConcreteElement角色类。

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

       第一:对扩展是开放的。在设计类时,若无特殊理由,必须要考虑到将来可能会扩展类。绝不能毫无理由地禁止扩展类。这就是“对扩展是开放的”的意思。

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

       功能需求总是在不断变化,而且这些功能需求大都是“希望扩展某个功能”。因此,如果不能比较容易地扩展类,开发过程将会变得非常困难。另一方面,如果要修改已经编写和测试完成的类,又可能会导致软件产品质量降低。

       对扩展开放、对修改关闭的类具有高可复用性,可作为组件复用。设计模式和面向对象的目的正是为我们提供一种结构,可以帮助我们设计出这样的类。

  • 易于增加ConcreteVisitor角色

       使用Visitor模式可以很容易地增加ConcreteVisitor角色。因为具体的处理被交给ConcreteVisitor角色负责,因此完全不用修改ConcreteElement角色。

  • 难以增加ConcreteElement角色

       虽然使用Visitor模式可以很容易地增加ConcreteVisitor角色,不过它却很难应对ConcreteElement角色的增加。

  • Visitor工作所需的条件

       “在Visitor模式中,对数据结构中的元素进行处理的任务被分离出来,交给Visitor类负责。这样,就实现类数据结构与处理的分离”这个主题,要达到这个目的是有条件的,那就是Element角色必须向Visitor角色公开足够多的信息。

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


相关的设计模式

       Iterator模式和Visitor模式都是在某种数据结构上进行处理。

       Iterator模式用于逐个遍历保存在数据结构中的元素。

       Visitor模式用于对保存在数据结构中的元素进行某种特定的处理。

       有时访问者所访问的数据结构会使用Composite模式。

  • Interpreter模式(第23章)

       在Interpreter模式中,有时会使用Visitor模式。例如,在生成了语法树后,可能会使用Visitor模式访问语法树的各个节点进行处理。


代码

  • Visitor(访问者)

       Visitor类是表示访问者的抽象类。访问者依赖于它所访问的数据结构。从外部调用visit方法时,程序会根据接收的参数的类型自动选择和执行相应的visit方法。这种方式称为方法的重载

public abstract class Visitor {

	public abstract void visit(File file);

	public abstract void visit(Directory directory);
}
  • Element(元素)

       Visitor类是表示访问者的类,而Element接口则是接受访问者的访问的接口。如果将Visitor比喻为玛利亚,Element接口就相当于住宿的地方(实现了Element接口的类的实例才是实际住宿的地方)。

       Element接口中声明了accept方法。该方法的参数是访问者Visitor类。

public interface Element {

	public abstract void accept(Visitor v);
}
public abstract class Entry implements Element {

	protected Entry parent; // 父节点

	public abstract String getName(); // 获取名字

	public abstract int getSize(); // 获取大小

	public Entry add(Entry entry) throws FileTreatMentException { // 增加目录条目
		throw new FileTreatMentException();
	}

	public Iterator<Entry> iterator() throws FileTreatMentException { // 生成Iterator
		throw new FileTreatMentException();
	}

	@Override
	public String toString() { // Template模式
		return getName() + " (" + getSize() + ")";
	}

}
  • ConcreteElement
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);
	}

}
public class Directory extends Entry {

	private String name; // 文件夹的名字
	private List<Entry> dir = new ArrayList<Entry>(); // 文件夹中目录条目的集合

	public Directory(String name) { // 构造函数
		this.name = name;
	}

	@Override
	public String getName() { // 获取名字
		return name;
	}

	@Override
	public int getSize() {
		SizeVisitor sizeVisitor = new SizeVisitor();
		accept(sizeVisitor);
		return sizeVisitor.getSize();
	}

	public Entry add(Entry entry) { // 增加目录条目
		dir.add(entry);
		entry.parent = this; // 加入文件夹时,记录父节点
		return this;
	}

	@Override
	public Iterator<Entry> iterator() throws FileTreatMentException {
		return dir.iterator();
	}

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

}
  • ConcreteVisitor(具体的访问者)
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<Entry> it = directory.iterator();
		while (it.hasNext()) {
			Entry entry = it.next();
			entry.accept(this);
		}
		currentdir = savedir;
	}

}
  • 异常类
public class FileTreatMentException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	public FileTreatMentException() {
	}

	public FileTreatMentException(String message) {
		super(message);
	}
}
  • Main类
public class Main {

	public static void main(String[] args) {
		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());

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

}

注:博客中的图片来自网上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值