1、前言html
什么叫访问,若是你们学过数据结构,对于这点就很清晰了,遍历就是访问的通常形式,单独读取一个元素进行相应的处理也叫做访问,读取到想要查看的内容+对其进行处理就叫做访问,那么咱们日常是怎么访问的呢,基本上就是直接拿着须要访问的地址来读写内存就能够了。java
为何还要有一个访问者模式呢,这就要放到OOP之中了,在面向对象编程的思想中,咱们使用类来组织属性,以及对属性的操做,那么咱们理所固然的将访问操做放到了类的内部,这样看起来没问题,可是咱们想要使用另外一种遍历方式要怎么办呢,咱们必须将这个类进行修改,这在设计模式中是大忌,在设计模式中就要保证,对扩展开发,对修改关闭的开闭原则。编程
所以,咱们思考,可不能够将访问操做独立出来变成一个新的类,当咱们须要增长访问操做的时候,直接增长新的类,原来的代码不须要任何的改变,若是能够这样作,那么咱们的程序就是好的程序,所以能够扩展,符合开闭原则。而访问者模式就是实现这个的,使得使用不一样的访问方式均可以对某些元素进行访问。设计模式
2、代码数据结构
package designMode.visitor; import sun.reflect.generics.visitor.Visitor; public interface Element { public abstract void accept(Visitor visitor); }
package designMode.visitor; import designMode.iterator.Iterator; public abstract class Entry implements Element{ public abstract String getName(); public abstract int getSize(); public abstract void printList(String prefix); public void printList(){ printList(""); } public Entry add(Entry entry) throws RuntimeException{ throw new RuntimeException(); } public Iterator iterator() throws RuntimeException{ throw new RuntimeException(); } @Override public String toString() { return getName()+"<"+getSize()+">"; } }
package designMode.visitor; import sun.reflect.generics.visitor.Visitor; 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 printList(String prefix) { System.out.println(prefix+"/"+this); } public void accept(Visitor visitor){ visitor.visit(this); } }
package designMode.visitor; import sun.reflect.generics.visitor.Visitor; import java.util.ArrayList; import java.util.Iterator; public class Directory extends Entry { String name; ArrayList entrys = new ArrayList(); public Directory(String name) { this.name = name; } @Override public String getName() { return name; } @Override public int getSize() { int size = 0; Iterator it = entrys.iterator(); while (it.hasNext()){ size += ((Entry)it.next()).getSize(); } return size; } @Override public Entry add(Entry entry){ entrys.add(entry); return this; } public Iterator iterator(){ return entrys.iterator(); } @Override public void printList(String prefix) { System.out.println(prefix+"/"+this); Iterator it = entrys.iterator(); Entry entry; while (it.hasNext()){ entry = (Entry) it.next(); entry.printList(prefix+"/"+name); } } public void accept(Visitor visitor){ visitor.visit(this); } }
package designMode.visitor; public abstract class Visitor { public abstract void visit(File file); public abstract void visit(Directory directory); }
package designMode.visitor; import java.util.Iterator; public class ListVisitor extends Visitor { 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 +="/"+directory.getName(); Iterator it = directory.iterator(); while (it.hasNext()){ Entry entry = (Entry) it.next(); entry.accept(this); } currentDir=saveDir; } }
package designMode.visitor; import java.util.ArrayList; import java.util.Iterator; public class FileVisitor extends Visitor { String currentDir = ""; String suffix; ArrayList files=new ArrayList(); public FileVisitor(String suffix){ this.suffix = suffix; } public void visit(File file) { if(file.getName().endsWith(suffix)){ // System.out.println(currentDir+"/"+file); files.add(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; } Iterator getFiles(){ return files.iterator(); } }
package designMode.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); Iterator it = visitor.getFiles(); while(it.hasNext()){ System.out.println(it.next()); } } }
运行结果第一个和使用Composite模式的结果同样,第二个是实现另外一种方式的访问,只访问文件后缀为某一特定的内容的文件,结果也是正确的,而且为了说明咱们的访问还能够保存下来访问的结果,咱们使用了ArrayList自带的迭代器将保存到ArrayList中的结果输出出来,咱们固然也能够直接在遍历的时候就输出出来,这个看咱们的使用要求了。所以能够看到在保证数据结构不发生变化的状况下,能够很是方便增长新的一种访问方法,只须要新增长一个访问类便可,可是若是咱们数据结构发生变化以后,就须要修改继承自Visitor类的全部类了,这也违背了开闭原则,所以咱们应该认真考虑,到底咱们的数据结构是定死的仍是常常变化的。没有任何一种设计模式是十全十美的,老是有所取舍,有所利弊,根据实际状况来选择才是最好的设计方法。ide
这里要说明一下双重分发机制,咱们来看一下最核心的遍历逻辑,结合组合模式的时候咱们已经分析过遍历方法,递归,你们以为此次咱们要怎么在数据结构外面进行遍历,确定还要使用递归,但是数据结构中的数据在类的内部,怎么递归到内部呢,咱们想到了间接递归,也就是双重分发。函数
public void printList(String prefix) { System.out.println(prefix+"/"+this); Iterator it=entrys.iterator(); Entry entry; while(it.hasNext()){ entry=(Entry)it.next(); entry.printList(prefix+"/"+name); } }
上面的代码是在组合模式类的内部遍历的过程,能够明确的看到递归(直接递归)的使用。咱们看一下访问者模式中的间接递归:学习
public void accept(Visitor visitor) { //System.out.println("开始访问文件夹:"+this); visitor.visit(this); //System.out.println("结束访问文件夹:"+this); //System.out.println(); }
public void accept(Visitor visitor) { //System.out.println("开始访问文件:"+this); visitor.visit(this); //System.out.println("结束访问文件:"+this); //System.out.println(); }
public void visit(File file) { System.out.println(currentDir+"/"+file); } public void visit(Directory directory) { System.out.println(currentDir+"/"+directory); String saveDir=currentDir; currentDir+=("/"+directory.getName()); Iterator it=directory.iterator(); while(it.hasNext()){ Entry entry=(Entry)it.next(); entry.accept(this); } currentDir=saveDir; }
咱们看到了entry.accept(this)这句话,这句话是很是重要的,咱们在Main中是这样用的:this
root.accept(new ListVisitor());
那么串连起来,在Main中咱们经过Directory或者File类型的对象调用accept(访问者)方法,接受访问者的访问,这是访问者和被访问者的第一次亲密接触,亲近对方就是为了得到对方的数据,而后才能对对方的数据进行使用,那么怎么拿到的呢?!咱们看到了这句visitor.visit(this);这句话无疑是重要的,被调用者告诉访问者,我将个人内容this,所有给你了,之后访问者就能够对this所指代的被访问者的内容进行操做了,分为两类,若是被访问者是File文件类型的,就会直接输出内容,到达叶子结点,访问结束;若是是文件夹,那就很是有意思了,首先咱们仍旧是让被访问者将本身的内容交给访问者visitor.visit(this);,以后public void visit(Directory directory)被调用,经过遍历的方式将属于这个文件夹下面的数据所有拿到Iterator it=directory.iterator();,而后开始一个个的处理,怎么处理呢,继续访问属于这个文件夹下面对象的accept()方法使用entry.accept(this);,来将访问者交过去,交给谁?!确定是给entry所指的对象,也就是文件夹里面的子文件夹或者文件,若是是文件的话,继续在本身的方法中调用visitor.visit(this);,最终落实到调用 public void visit(File file)经过System.out.println(currentDir+"/"+file);访问结束,若是不是文件呢?若为文件夹,则继续调用属于文件夹的方法,就这样不断地往下面查找,一直到遍历完文件夹下面的全部的元素,所以也是深度优先遍历。就这样经过压栈和出栈,咱们完成了最终的遍历,最终的出口有两个,一个是访问文件,输出以后结束,另外一个是遍历完文件夹,即便文件夹下面没有文件依旧结束。spa
root.accept(new ListVisitor());
public void visit(File file) { System.out.println(currentDir+"/"+file); } public void visit(Directory directory) { System.out.println(currentDir+"/"+directory); String saveDir=currentDir; currentDir+=("/"+directory.getName()); Iterator it=directory.iterator(); while(it.hasNext()){ Entry entry=(Entry)it.next(); entry.accept(this); } currentDir=saveDir; }
在accept函数中调用visit,一样在visit中调用accept,这就是间接递归,或者叫作双重分发。产生的缘由就是访问者须要和被访问者相互交流,才能一步步的获得想要的数据。咱们能够考虑主持人采访一个明星,那么这个明星接受采访,把本身基本信息(能问的问题以及某些答案)告诉主持人,问主持人有问题吗?若是主持人有问题(还能向下问)要问那么就再次拿着新的问题问这个明星,这个明星再次将本身关于这方面的信息告诉主持人;若是没有问题(获得答案),主持人将信息总结以后说出来。就这样一直持续下去,直到主持人没问题问了,而且明星的信息也都被问到了,这样采访就结束了。因而可知,不少时候设计模式都是和生活密切相关的,生活中的常识有时候就是一些套路,而这种套路就是一种抽象的模式。
3、总结
访问者模式是一个很是有意思的模式,由于本身须要获得数据就须要向被访者索取,若是可以一次索取成功,访问就结束了,若是还须要其余信息,则再次向被访问者索取,就这样知道拿到本身须要的全部数据。在本例中借用了组合模式中的数据结构,那是由于这种树形的结构很适合咱们进行递归访问。访问者模式和迭代器模式都是在某种数据结构上进行处理,一种是对数据结构中的元素进行某种特定的处理,另外一种是用某种方式遍历全部元素。在实际应用中,咱们根据实际须要来考虑是否是须要双重分发机制。在本例中的访问者模式中用到了组合模式、委托(组合)、双重分发等原理,便于新增访问方式,不便于对数据结构的修改。