工程师是代码数量,经理是产品数量,他们的职责不一样,也就是因为差异性,才使得访问模式能够发挥它的作用。Staff、Engineer、Manager 3个类型就是对象结构,这些类型相对稳定,不会发生变化。
然后将这些员工添加到一个业务报表类中,公司高层可以通过该报表类的 showReport 方法查看所有员工的业绩,具体代码如下:
// 员工业务报表类 public class BusinessReport { private List<Staff> mStaffs = new LinkedList<>(); public BusinessReport() { mStaffs.add(new Manager("经理-A")); mStaffs.add(new Engineer("工程师-A")); mStaffs.add(new Engineer("工程师-B")); mStaffs.add(new Engineer("工程师-C")); mStaffs.add(new Manager("经理-B")); mStaffs.add(new Engineer("工程师-D")); } /** * 为访问者展示报表 * @param visitor 公司高层,如CEO、CTO */ public void showReport(Visitor visitor) { for (Staff staff : mStaffs) { staff.accept(visitor); } } }
下面看看 Visitor 类型的定义, Visitor 声明了两个 visit 方法,分别是对工程师和经理对访问函数,具体代码如下:
public interface Visitor { // 访问工程师类型 void visit(Engineer engineer); // 访问经理类型 void visit(Manager manager); }
首先定义了一个 Visitor 接口,该接口有两个 visit 函数,参数分别是 Engineer、Manager,也就是说对于 Engineer、Manager 的访问会调用两个不同的方法,以此达成区别对待、差异化处理。具体实现类为 CEOVisitor、CTOVisitor类,具体代码如下:
// CEO访问者 public class CEOVisitor implements Visitor { @Override public void visit(Engineer engineer) { System.out.println("工程师: " + engineer.name + ", KPI: " + engineer.kpi); } @Override public void visit(Manager manager) { System.out.println("经理: " + manager.name + ", KPI: " + manager.kpi + ", 新产品数量: " + manager.getProducts()); } }
重载的 visit 方法会对元素进行不同的操作,而通过注入不同的 Visitor 又可以替换掉访问者的具体实现,使得对元素的操作变得更灵活,可扩展性更高,同时也消除了类型转换、if-else 等“丑陋”的代码。
下面是客户端代码:
public class Client { public static void main(String[] args) { // 构建报表 BusinessReport report = new BusinessReport(); System.out.println("=========== CEO看报表 ==========="); report.showReport(new CEOVisitor()); System.out.println("=========== CTO看报表 ==========="); report.showReport(new CTOVisitor()); } }
具体输出如下:
=========== CEO看报表 =========== 经理: 经理-A, KPI: 9, 新产品数量: 0 工程师: 工程师-A, KPI: 6 工程师: 工程师-B, KPI: 6 工程师: 工程师-C, KPI: 8 经理: 经理-B, KPI: 2, 新产品数量: 6 工程师: 工程师-D, KPI: 6 =========== CTO看报表 =========== 经理: 经理-A, 产品数量: 3 工程师: 工程师-A, 代码行数: 62558 工程师: 工程师-B, 代码行数: 92965 工程师: 工程师-C, 代码行数: 58839 经理: 经理-B, 产品数量: 6 工程师: 工程师-D, 代码行数: 53125
在上述示例中,Staff 扮演了 Element 角色,而 Engineer 和 Manager 都是 ConcreteElement;CEOVisitor 和 CTOVisitor 都是具体的 Visitor 对象;而 BusinessReport 就是 ObjectStructure;Client就是客户端代码。
访问者模式最大的优点就是增加访问者非常容易,我们从代码中可以看到,如果要增加一个访问者,只要新实现一个 Visitor 接口的类,从而达到数据对象与数据操作相分离的效果。如果不实用访问者模式,而又不想对不同的元素进行不同的操作,那么必定需要使用 if-else 和类型转换,这使得代码难以升级维护。
优点:
1、解耦数据结构与数据操作,使得操作集合可以独立变化
2、扩展性好,可以通过扩展访问者角色,实现对数据集的不同操作
3、各个角色各司其职,符合单一职责原则。
缺点:
1、无法增加元素类型,若系统数据结构对象易于变化,有新的数据对象增加进来,访问者增加对应元素操作,违背开闭原则
2、具体元素变更困难,具体元素增加属性,删除属性等操作会导致对应访问者类需要进行修改,如有大量访问者者时,修改范围太大。
访问者模式源码应用
1、JDK NIO FileVisitor
2、Spring IoC BeanDefinitionVisitor
访问者模式一篇就够了