首页 > 代码库 > 10 行为型模式之 - 访问者模式
10 行为型模式之 - 访问者模式
访问者模式介绍:访问者模式是一种将数据操作与数据结构分离的设计模式,它是《设计模式》中23种设计模式中最复杂的一个,但是它的使用频率并不高,正如《设计模式》的作者GOF对访问者模式的描述:大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是真地需要它了。
访问者模式的基本想法是,软件系统中拥有一个由许多对象构成的,比较稳定的对象结构,这些对象的类都拥有一个accept方法,用来接受访问者对象的访问。访问者是一个接口,它拥有一个visit方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果
访问者模式的定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义 作用于这些元素的新的操作。
访问者模式使用的场景:
1 对象结构比较稳定,但是经常需要在此对象结构上定义新的操作
2 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类
简单示例:
在年终时,公司都会给员工进行业绩考核,以此来评定该员工的绩效及年终奖,晋升等,这些评定都是由公司高层来负责,但是,不同领域的管理人员对员工的评定是不一样的,为了简单明了地说明问题,我们简单地把员工分为工程师和经理,评定员工的分别为CEO和CTO,我们假定CTO只关注工程师的代码量,经理的产品数量。而CEO关注的是工程师的KPI和经理的KPI以及产品的数量,从中可以看出,CEO和CTO对于不同员工的关注点是不一样的,这就需要对不同的员工类型进行不同的处理,访问者模式此时可以派上用场了,我们看看下面相关代码的实现
员工基类:
1 /** 2 * 员工基类 3 */ 4 public abstract class Staff { 5 public String name; 6 7 //员工KPI 8 public int kpi; 9 10 public Staff(String name){ 11 this.name = name; 12 kpi = new Random().nextInt(); 13 } 14 15 // 接受visitor的访问 16 public abstract void accept(Visitor visitor); 17 }
Staff类定义了员工的基本信息及一个accept方法,accept方法表示接受访问者的访问,由子类具体实现。下面看看工程师和经理的代码:
1 /** 2 * 工程师类型 3 */ 4 public class Engineer extends Staff { 5 public Engineer(String name){ 6 super(name); 7 } 8 9 @Override 10 public void accept(Visitor visitor) { 11 visitor.visit(this); 12 } 13 14 //工程师这一年写的代码数量 15 public int getCodeLine(){ 16 return new Random().nextInt() * 10000; 17 } 18 }
/** * 经理类型 */ public class Manager extends Staff{ public Manager(String name){ super(name); } @Override public void accept(Visitor visitor) { visitor.visit(this); } //经理一年内做的产品的数量 public int getProducts(){ return 10; } }
在工程师类中添加了获取代码行数的函数,而在经理类中则添加了获取新产品数量的函数,它们的职责是不一样的,也正是由于它们的差异性才使得访问者模式能够发挥它的作用。 Staff , Engineer , Manager 3个类型就是对象结构,这些类型相对稳定,不会发生变化。
然后将这些员工添加到一个业务报表类中,公司高层可以通过该报表类的showReport函数查看所有员工的业绩。具体代码如下:
1 /** 2 * 员工业务报表 3 */ 4 public class BusinessReport { 5 List<Staff> mStaffs = new ArrayList<>(); 6 7 public BusinessReport(){ 8 mStaffs.add(new Manager("王经理")); 9 mStaffs.add(new Engineer("工程师-小A")); 10 mStaffs.add(new Engineer("工程师-小B")); 11 mStaffs.add(new Engineer("工程师-小C")); 12 } 13 14 /** 15 * 为访问者展示报表,公司高层,如CEO,CTO 16 */ 17 public void showReport(Visitor visitor){ 18 for (Staff staff : mStaffs){ 19 staff.accept(visitor); 20 } 21 } 22 }
下面看看访问者 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()); } }
/** * CTO 类型,只关注技术层面上的贡献 */ public class CTOVisitor implements Visitor{ @Override public void visit(Engineer engineer) { System.out.println("工程师 : "+engineer.name + " , 代码行数:" + engineer.getCodeLine()); } @Override public void visit(Manager manager) { System.out.println("经理 : "+manager.name + " , 产品数量:" + manager.getProducts()); } }
在CEO的访问者中,CEO只关注Engineer员工的KPI,而对于Manager类型的员工除了KPI之外还有该Manager本年度新开发产品的数量,两类员工的关注点不同,通过两个visitor方法分别进行处理。而如果不使用访问者模式,只是通过一个visitor函数进行处理,那么就需要在这个visit函数中对不同的员工类进行判断,然后分别处理,代码大致如下:
1 /** 2 * 不使用访问者模式的写法 3 */ 4 public class ReportUtils { 5 public void visit(Staff staff){ 6 if(staff instanceof Engineer){ 7 Engineer engineer = (Engineer) staff; 8 System.out.println("工程师:" + engineer.name + " , KPI = "+ engineer.kpi); 9 }else if(staff instanceof Manager){ 10 Manager manager = (Manager) staff; 11 System.out.println("经理:"+manager.name + " , KPI : "+manager.kpi + ",新产品数量 : "+ manager.getProducts()); 12 } 13 } 14 }
这就导致了if else 逻辑的嵌套以及类型的强制转换,难以维护和扩展,当类型较多时,这个ReportUtils就会很复杂,而使用Visitor模式,通过同一个函数对不同的元素类型进行相应处理,使得结构更加清晰,灵活性更高
下面是使用访问者模式的客户端的用法,代码如下:
1 /** 2 * 客户端 3 */ 4 public class Client { 5 public static void main(String[] args){ 6 //构建报表 7 BusinessReport businessReport = new BusinessReport(); 8 System.out.println("========== 给CEO看的报表 ============"); 9 businessReport.showReport(new CEOVisitor()); 10 System.out.println("========== 给CTO看的报表 ============"); 11 businessReport.showReport(new CTOVisitor()); 12 } 13 }
客户端代码中,首先构建了一个报表对象,该对象中维护了所有员工的集合,然后通过报表类的showReport函数为Visitor对象提供一个访问接口,在这个函数中遍历所有的员工,然后调用员工的accept函数接受访问者的访问,每个访问者对不同类型的员工调用对应的visit函数实现不同的操作
具体输出如下:
========== 给CEO看的报表 ============
经理:王经理 KPI : -521144346 , 新产品数量:10
工程师:工程师-小A KPI : -1619137302
工程师:工程师-小B KPI : -237723895
工程师:工程师-小C KPI : 1468883099
========== 给CTO看的报表 ============
经理 : 王经理 , 产品数量:10
工程师 : 工程师-小A , 代码行数:646642576
工程师 : 工程师-小B , 代码行数:-1775007488
工程师 : 工程师-小C , 代码行数:-1605653216
访问者模式最大的优点就是增加访问者非常容易。我们从代码中可以看到,如果要增加一个访问者,你新创建一个实现了Visitor接口的类,然后实现两个visitor函数来对不同的元素进行不同的操作,从而达到数据对象与数据操作相分离的效果,如果不使用访问者模式,而又想对不同的元素进行不同的操作,那么必定需要使用 if else 和类型转换,这使得代码难以维护和升级,此时,访问者模式的作用就体现出来了。
10 行为型模式之 - 访问者模式