Visitor模式及其实现方式优劣比较
Visitor模式的使用情景以及与Iterator的比较见我以前的一篇文章
http://fuliang.iteye.com/blog/166142
这次主要讨论Visitor的两种实现方式及其优劣。
主要实现方式主要有两种:
方法一、节点类的accept方法负责遍历逻辑,visitor只负责访问的操作。
方法二、节点类负责调用visit自己,而visitor除了负责访问该节点外还accept子节点,也就是遍历逻辑。
第一种看起来很优美,因为它将访问操作和节点遍历分离开来,职责清晰,并且节点本身知道如何去遍历
子节点,事实上这也是GOF设计模式一书例子中使用的方法。缺点是遍历逻辑在节点中写死了,也就是说
如果我在节点中定义了一种遍历顺序,如果Visitor试图使用其他的遍历顺序变得不可能,另外如果我想在
遍历之前做一些事情,遍历之后做些事情也同时变得不可能,除非每个visit方法提供了提供previousVisit
和postVisit进行回调,然而这显然增加了api的复杂性。
第二种方法,很容易看到缺点,他混淆了访问和遍历的职责,这与单一职责原则相悖,后面会讨论如何避免这个缺点。但是它非常的灵活,
可以避免第一方法带来的缺点。
下面举一个简单的例子(这个例子来自于实际项目的一个精简)来说明一下上述两种实现方式,以及面临的问题,
我们先定义一个定义的节点类型,其定义了一种树形结构:
所有节点类要实现的接口,包含了accept方法。
package edu.jlu.fuliang.model;import edu.jlu.fuliang.visitor.IVisitor;public interface IModel {void accept(IVisitor visitor);}package edu.jlu.fuliang.model;import java.util.ArrayList;import java.util.Collections;import java.util.List;import edu.jlu.fuliang.visitor.IVisitor;public class JavaProject implements IModel {private String javaProjectName;private List<JavaPackage> javaPackages;public JavaProject() {javaPackages = new ArrayList<JavaPackage>();}public void addPackage(JavaPackage pkg) {javaPackages.add(pkg);}public List<JavaPackage> getJavaPackages() {return Collections.unmodifiableList(javaPackages);}public String getJavaProjectName() {return javaProjectName;}public void setJavaProjectName(String javaProjectName) {this.javaProjectName = javaProjectName;}@Overridepublic void accept(IVisitor visitor) {visitor.visitProject(this); for (JavaPackage javaPackage : javaPackages) {javaPackage.accept(visitor);}}}package edu.jlu.fuliang.model;import java.util.ArrayList;import java.util.Collections;import java.util.List;import edu.jlu.fuliang.visitor.IVisitor;public class JavaPackage implements IModel {private String packageName;private List<JavaClass> javaClasses;public JavaPackage() {javaClasses = new ArrayList<JavaClass>();}@Overridepublic void accept(IVisitor visitor) {visitor.visitPackage(this);for (JavaClass javaClass : javaClasses) {javaClass.accept(visitor);}}public String getPackageName() {return packageName;}public void setPackageName(String packageName) {this.packageName = packageName;}public List<JavaClass> getJavaClasses() {return Collections.unmodifiableList(javaClasses);}public void addJavaClass(JavaClass javaClass){javaClasses.add(javaClass);}}package edu.jlu.fuliang.model;import edu.jlu.fuliang.visitor.IVisitor;public class JavaClass implements IModel{private String className;public JavaClass() {}public String getClassName() {return className;}public void setClassName(String className) {this.className = className;}@Overridepublic void accept(IVisitor visitor) {visitor.visitClass(this);}}package edu.jlu.fuliang.visitor;import edu.jlu.fuliang.model.JavaClass;import edu.jlu.fuliang.model.JavaPackage;import edu.jlu.fuliang.model.JavaProject;public interface IVisitor {public void visitProject(JavaProject project);public void visitPackage(JavaPackage pkg);public void visitClass(JavaClass klass);}package edu.jlu.fuliang.visitor;import edu.jlu.fuliang.model.JavaClass;import edu.jlu.fuliang.model.JavaPackage;import edu.jlu.fuliang.model.JavaProject;public class PrintVistor implements IVisitor{@Overridepublic void visitClass(JavaClass klass) {System.out.println(klass.getClassName());}@Overridepublic void visitPackage(JavaPackage pkg) {System.out.println(pkg.getPackageName());}@Overridepublic void visitProject(JavaProject project) {System.out.println(project.getJavaProjectName());}}package edu.jlu.fuliang.visitor;import java.util.List;import edu.jlu.fuliang.model.JavaClass;import edu.jlu.fuliang.model.JavaPackage;import edu.jlu.fuliang.model.JavaProject;public class PrettyPrintVisitor implements IVisitor{ private Spacing spacing = new Spacing(3);@Overridepublic void visitClass(JavaClass klass) {System.out.print(spacing);spacing.updateSpc(1);System.out.println(klass.getClassName());spacing.updateSpc(-1);}@Overridepublic void visitPackage(JavaPackage pkg) {System.out.print(spacing);System.out.println(pkg.getPackageName());spacing.updateSpc(1);List<JavaClass> javaClasses = pkg.getJavaClasses();for (JavaClass javaClass : javaClasses) {javaClass.accept(this);}spacing.updateSpc(-1);}@Overridepublic void visitProject(JavaProject project) {System.out.print(project.getJavaProjectName());spacing.updateSpc(1);List<JavaPackage> javaPackages = project.getJavaPackages();for (JavaPackage javaPackage : javaPackages) {javaPackage.accept(this);}spacing.updateSpc(-1);}}package edu.jlu.fuliang.visitor;public class Spacing {public final int INDENT_AMT;// 缩进的字数public String spc = " ";public String brunch = "|---->";public int indentLevel;// 缩紧的层次public Spacing(int indentAmount) {INDENT_AMT = indentAmount;}public String toString() {return spc;}public void updateSpc(int numIndentLvls) {if (spc.length() >= brunch.length())spc = spc.substring(0, spc.length() - brunch.length());indentLevel += numIndentLvls;if (numIndentLvls < 0) {spc = spc.substring(0, indentLevel * INDENT_AMT);spc += brunch;} else if (numIndentLvls > 0) {StringBuffer buf = new StringBuffer(spc);for (int i = 0; i < numIndentLvls * INDENT_AMT; ++i)buf.append(" ");buf.append(brunch);spc = buf.toString();}}}package edu.jlu.fuliang.visitor;import java.util.List;import edu.jlu.fuliang.model.JavaClass;import edu.jlu.fuliang.model.JavaPackage;import edu.jlu.fuliang.model.JavaProject;public class PreOrderDFSVisitor implements IVisitor{@Overridepublic void visitClass(JavaClass klass) {}@Overridepublic void visitPackage(JavaPackage pkg) {List<JavaClass> javaClasses = pkg.getJavaClasses();for (JavaClass javaClass : javaClasses) {javaClass.accept(this);}}@Overridepublic void visitProject(JavaProject project) {List<JavaPackage> javaPackages = project.getJavaPackages();for (JavaPackage javaPackage : javaPackages) {javaPackage.accept(this);}}}package edu.jlu.fuliang.visitor;import java.util.List;import edu.jlu.fuliang.model.JavaClass;import edu.jlu.fuliang.model.JavaPackage;import edu.jlu.fuliang.model.JavaProject;public class PrettyPrintVisitor extends PreOrderDFSVisitor{ private Spacing spacing = new Spacing(3);@Overridepublic void visitClass(JavaClass klass) {System.out.print(spacing);spacing.updateSpc(1);System.out.println(klass.getClassName()); super.visitClass(klass);spacing.updateSpc(-1);}@Overridepublic void visitPackage(JavaPackage pkg) {System.out.print(spacing);System.out.println(pkg.getPackageName());spacing.updateSpc(1);super.visitPackage(pkg);spacing.updateSpc(-1);}@Overridepublic void visitProject(JavaProject project) {System.out.print(project.getJavaProjectName());spacing.updateSpc(1);super.visitProject(project);spacing.updateSpc(-1);}}