访问模式是23种模式中最复杂的一个。目的在于将数据操作与数据结构进行分离。一个系统由许多对象组成,每个对象都有一个accept操作来接收访问者访问,对象会调用访问者的visit方法传入该对象,使得访问者可以访问处理对象结构中的每个元素。访问者是一个接口,又根据实现不同的访问者来达到对系统类的不同访问实现。
Android中APT(Annotation Processing Tools)则是使用到了该模式,编译的时候编译器检查AbstractProcessor的子类,并且调用该类的process函数,然后将添加注解的元素都传递到process函数中,使得开发人员可以在编译器进行相应处理,例如根据注解生成新的java类,也是我们常用到的ButterKnife的基本原理
封装一些作用于某种数据结构中个元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
利用书中的一个例子,年中工程师、经理需要提供报表给领导看:
那么想想UML的结构,这里就比较适合了,工程师和经理是比较稳定的元素,只是CEO和CTO这关心的不一样,CEO和CTO就可以看作为访问者了。那么我们来实现一下
/** * 职员接口 * 1. 提供访问者方法 */ public abstract class Staff { public Staff(String name, int kpi) { this.name = name; this.kpi = kpi; } /** * 职员名称 */ protected String name; /** * 职员kpi */ protected int kpi; public abstract void accept(AbsVisitor absVisitor); }
/** * 工程师 */ public class Employee extends Staff { private int codeLines; public Employee(String name, int kpi) { super(name, kpi); codeLines = new Random().nextInt(10_000_000); } /** * 接收访问者访问,调用访问者的访问方法 * * @param absVisitor */ @Override public void accept(AbsVisitor absVisitor) { //调用访问者的访问方法,并传入自己 absVisitor.visit(this); } /** * 获取工程师代码行数 */ public int getCodeLines() { return codeLines; } } /** * 经理 */ public class Manager extends Staff { private int products; public Manager(String name, int kpi) { super(name, kpi); products = new Random().nextInt(100); } /** * 接收访问者访问,调用访问者的访问方法 * * @param absVisitor */ @Override public void accept(AbsVisitor absVisitor) { absVisitor.visit(this); } /** * 获取产品数量 * * @return */ public int getProducts() { return products; } }
这里的工程师和经理提供各自对外的方法,然后也实现accept方法,调用访问者的visit方法,访问者接下来定义
/** * 访问者 * CEO、CTO */ public abstract class AbsVisitor { /** * 访问方法,传入管理者 * * @param staffManager */ abstract void visit(Manager staffManager); /** * 访问方法,传入普通职员 * * @param employee */ abstract void visit(Employee employee); }
这里没有使用里氏替换来只使用一个方法(参数为Staff),就是出于这个模式要求结构稳定,没必要那样采用,而且那样定参数的话,调用的适合依旧需要判断是哪个对象,会多很多if-else,所以没必要折腾
/** * 访问者 * CEO */ public class CEOVisitor extends AbsVisitor { /** * CEO关心的管理者 * kpi、产品数量 * * @param staffManager */ @Override void visit(Manager staffManager) { System.out.println("CTO 关心: manager name:" + staffManager.name + ",kpi:" + staffManager.kpi + ",products:" + staffManager.getProducts()); } /** * CEO关心的工程师 * * @param employee */ @Override void visit(Employee employee) { System.out.println("CEO 关心: employee name:" + employee.name + ",kpi:" + employee.kpi); } } /** * 访问者 * CTO */ public class CTOVisitor extends AbsVisitor { /** * CTO关心的管理者 * 产品数量 * @param staffManager */ @Override void visit(Manager staffManager) { System.out.println("CTO 关心: manager name:" + staffManager.name + ",products:" + staffManager.getProducts()); } /** * CTO关心的工程师 * 代码行数 * @param employee */ @Override void visit(Employee employee) { System.out.println("CTO 关心: employee name:" + employee.name + ",codeLies:" + employee.getCodeLines()); } }
//先收集工程师和经理信息 List<Staff> staffs = new ArrayList<>(); staffs.add(new Employee("王工程师", 5)); staffs.add(new Employee("李工程师", 9)); staffs.add(new Employee("大工程师", 10)); staffs.add(new Manager("周经理", 10)); staffs.add(new Manager("杨经理", 7)); //发送给CTO看,一个循环 System.out.println("===CTO看的报表==="); CTOVisitor ctoVisitor = new CTOVisitor(); for (Staff staff : staffs) { //利用accept接收访问者,然后在内部调用访问者测访问方法 staff.accept(ctoVisitor); } CEOVisitor ceoVisitor = new CEOVisitor(); System.out.println("===CEO看的报表==="); for (Staff staff : staffs) { staff.accept(ceoVisitor); }
这里没有单独去定义UML中的ObjectStructrue,可以在5测试代码中体现出来,要ObjectStructrue的话,就是携带列表,并提供一个遍历方法(传入AbsVisitor)即可。
===CTO看的报表=== CTO 关心: employee name:王工程师,codeLies:2767421 CTO 关心: employee name:李工程师,codeLies:2102321 CTO 关心: employee name:大工程师,codeLies:7722955 CTO 关心: manager name:周经理,products:11 CTO 关心: manager name:杨经理,products:89 ===CEO看的报表=== CEO 关心: employee name:王工程师,kpi:5 CEO 关心: employee name:李工程师,kpi:9 CEO 关心: employee name:大工程师,kpi:10 CTO 关心: manager name:周经理,kpi:10,products:11 CTO 关心: manager name:杨经理,kpi:7,products:89
总结:访问者模式的思想即是在通过提供访问类来处理一些具有稳定结构元素的访问操作,这样对访问者则是可灵活扩展了。一般情况是不需要用它,如果需要用它的时候,那么可能是真的需要它了。需要根据实际情况考虑是否需要用它,包括其他模式也是一样,根据使用场景和优缺点来看能解决什么问题,然后考虑要不要用它,避免滥用设计模式的情况。