介绍 访问者模式能将算法与其所作用的对象隔离开来。在访问者模式(Visitor Pattern)中,使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作
为什么还要有一个访问者模式呢? 这就要放到OOP之中了,在面向对象编程的思想中,我们使用类来组织属性,以及对属性的操作,那么我们理所当然的将访问操作放到了类的内部,这样看起来没问题,但是当我们想要使用另一种遍历方式要怎么办呢,我们必须将这个类进行修改,这在设计模式中是大忌,在设计模式中就要保证,对扩展开放,对修改关闭的开闭原则。
所以我们考虑 可不可以将访问操作独立出来变成一个新的le我们需要增加访问操作的时候,直接增加新的类,原来的代码不需要任何的改变,如果可以这样做,那么我们的程序就是好的程序,因为可以扩展,符合开闭原则。而访问者模式就是实现这个的,使得使用不同的访问方式都可以对某些元素进行访问。
实现
Visitor类是表示访问者的抽象类 Visitor.java
1 2 3 4 5 6 7 package com.edu.tju.GOF.Visitor;public abstract class Visitor { public abstract void visit (File file) ; public abstract void visit (Directory directory) ; }
Element接口是接受访问者的访问男的接口
1 2 3 4 5 package com.edu.tju.GOF.Visitor;public interface Element { public abstract void accept (Visitor v) ; }
Entry类再本质上 与 之前的Composite中的Entry类是一样的,不过这次实现了Element接口为了让Entry类适用于Visitor模式.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.edu.tju.GOF.Visitor;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(); } public String toString () { return getName() + " (" + getSize() + ")" ; } }
File.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.edu.tju.GOF.Visitor;public class File extends Entry { private String name; private int size; public File (String name, int size) { this .name = name; this .size = size; } public String getName () { return name; } public int getSize () { return size; } public void accept (Visitor v) { v.visit(this ); } }
Directory.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.edu.tju.GOF.Visitor;import java.util.Iterator;import java.util.ArrayList;public class Directory extends Entry { private String name; private ArrayList dir = new ArrayList(); public Directory (String name) { this .name = name; } public String getName () { return name; } public int getSize () { int size = 0 ; Iterator it = dir.iterator(); while (it.hasNext()) { Entry entry = (Entry) it.next(); size += entry.getSize(); } return size; } public Entry add (Entry entry) { dir.add(entry); return this ; } public Iterator iterator () { return dir.iterator(); } public void accept (Visitor v) { v.visit(this ); } }
ListVistor.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.edu.tju.GOF.Visitor;import java.util.Iterator;public class ListVisitor extends Visitor { private String currentdir = "" ; public void visit (File file) { System.out.println(currentdir + "/" + file); } 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; } }
FileVisitor.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.edu.tju.GOF.Visitor;import java.util.ArrayList;import java.util.Iterator;public class FileVisitor extends Visitor { String currentDir = "" ; String suffix; public FileVisitor (String suffix) { this .suffix = suffix; } public void visit (File file) { if (file.getName().endsWith(suffix)) { System.out.println(currentDir + "/" + file); } } public void visit (Directory directory) { String saveDir = currentDir; currentDir += ("/" + directory.getName()); Iterator it = directory.iterator(); while (it.hasNext()) { Entry entry = (Entry) it.next(); entry.accept(this ); } currentDir = saveDir; } }
Main.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package com.edu.tju.GOF.Visitor;import java.util.Iterator;public class Main { public static void main (String[] args) { Directory root = new Directory("根目录" ); Directory life = new Directory("我的生活" ); File eat = new File("吃火锅.txt" , 100 ); File sleep = new File("睡觉.html" , 100 ); File study = new File("学习.txt" , 100 ); life.add(eat); life.add(sleep); life.add(study); Directory work = new Directory("我的工作" ); File write = new File("写博客.doc" , 200 ); File paper = new File("写论文.html" , 200 ); File homework = new File("写家庭作业.docx" , 200 ); work.add(write); work.add(paper); work.add(homework); Directory relax = new Directory("我的休闲" ); File music = new File("听听音乐.js" , 200 ); File walk = new File("出去转转.psd" , 200 ); relax.add(music); relax.add(walk); Directory read = new Directory("我的阅读" ); File book = new File("学习书籍.psd" , 200 ); File novel = new File("娱乐小说.txt" , 200 ); read.add(book); read.add(novel); root.add(life); root.add(work); root.add(relax); root.add(read); root.accept(new ListVisitor()); System.out.println("========================" ); FileVisitor visitor = new FileVisitor(".psd" ); root.accept(visitor); } }
总结 访问者模式是一个非常有意思的模式,因为自己需要得到数据就需要向被访者索取,如果能够一次索取成功,访问就结束了,如果还需要其他信息,则再次向被访问者索取,就这样知道拿到自己需要的所有数据。在本例中借用了组合模式中的数据结构,那是因为这种树形的结构很适合我们进行递归访问。访问者模式和迭代器模式都是在某种数据结构上进行处理,一种是对数据结构中的元素进行某种特定的处理,另一种是用某种方式遍历所有元素。在实际应用中,我们根据实际需要来考虑是不是需要双重分发机制。在本例中的访问者模式中用到了组合模式、委托(组合)、双重分发等原理,便于新增访问方式,不便于对数据结构的修改。
Visitor模式 登场的橘色
Visitor(访问者,抽象类)
ConcreteVisitor(具体的访问者,实现visit接口)
Element(访问对象、抽象类)
ConcreteElement(具体的访问对象)
ObjectStructure(对象结构,遍历Element,处理Element角色的集合)
拓展
双重分发机制 在Visitor模式中方法的调用关系 accept(接受)发给发的调用方式如下
element.accept(visitor);
在visit(访问)方法的调用方式如下:
visitor.visit(element);
对比这两个方法,他们是相反的关系,element接受visitor,而visitor又访问element.
思考题
在示例程序中,Directory类的gitSize方法的作用是获取文件夹大小,请编写一个获取大小的SizeVisitor类,用它替换掉Directory类的getSize方法.