一:访问者模式定义
--->封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
二:访问者模式角色
● Visitor——抽象访问者
抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。
● ConcreteVisitor——具体访问者
它影响访问者访问到一个类后该怎么干,要做什么事情。
● Element——抽象元素
接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。
● ConcreteElement——具体元素
实现accept方法,通常是visitor.visit(this),基本上都形成了一种模式了。
● ObjectStruture——结构对象
元素产生者,一般容纳在多个不同类、不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。
三:访问者模式的应用
【1】访问者模式的优点
● 符合单一职责原则
具体元素角色也就是Employee抽象类的两个子类负责数据的加载,而Visitor类则负责报表的展现,两个不同的职责非常明确地分离开来,各自演绎变化。
● 优秀的扩展性
由于职责分开,继续增加对数据的操作是非常快捷的,例如,现在要增加一份给大老板的报表,这份报表格式又有所不同,直接在Visitor中增加一个方法,传递数据后进行整理打印。
● 灵活性非常高
不同的访问者不同的操作
【2】访问者模式的缺点
● 具体元素对访问者公布细节
访问者要访问一个类就必然要求这个类公布一些方法和数据,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的。
● 具体元素变更比较困难
具体元素角色的增加、删除、修改都是比较困难的,就上面那个例子,你想想,你要是想增加一个成员变量,如年龄age,Visitor就需要修改,如果Visitor是一个还好办,多个呢?业务逻辑再复杂点呢?
● 违背了依赖倒置转原则
访问者依赖的是具体元素,而不是抽象元素,这破坏了依赖倒置原则,特别是在面向对象的编程中,抛弃了对接口的依赖,而直接依赖实现类,扩展比较难。
【3】访问者模式的应用场景
● 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式已经不能胜任的情景。
● 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
● 总结一下,在这种地方你一定要考虑使用访问者模式:业务规则要求遍历多个不同的对象。这本身也是访问者模式出发点,迭代器模式只能访问同类或同接口的数据(当然了,如果你使用instanceof,那么能访问所有的数据,这没有争论),而访问者模式是对迭代器模式的扩充,可以遍历不同的对象,然后执行不同的操作,也就是针对访问的对象不同,执行不同的操作。访问者模式还有一个用途,就是充当拦截器(Interceptor)角色,这个我们将在混编模式中讲解。
四:访问者模式中涉及的概念:双分派
---->说到访问者模式就不得不提一下双分派(double dispatch)问题,什么是双分派呢?我们先来解释一下什么是单分派(single dispatch)和多分派(multiple dispatch),
---->单分派:处理一个操作是根据请求者的名称和接收到的参数决定的,在Java中有静态绑定和动态绑定之说,它的实现是依据重载(overload)和覆写(override)实现的
---->重载在编译器期就决定了要调用哪个方法,它是根据role的表面类型而决定调用act(Role role)方法,这是静态绑定;而Actor的执行方法act则是由其实际类型决定的,这是动态绑定。
---->看到没?不管演员类和角色类怎么变化,我们都能够找到期望的方法运行,这就是双反派。双分派意味着得到执行的操作决定于请求的种类和两个接收者的类型,它是多分派的一个特例。从这里也可以看到Java是一个支持双分派的单分派语言。
五:访问者模式案例
【1】数据公共部分
1 package com.yeepay.sxf.template20; 2 /** 3 * 数据公共部分 4 * @author sxf 5 * 6 */ 7 public abstract class Employee { 8 public final static int MALE=0;//0代表是男性 9 public final static int FEMALE=1;//1代表是女性 10 //甭管是谁,都有工资 11 private String name; 12 //薪水 13 private int salary; 14 //性别很重要 15 private int sex; 16 //构造函数 17 public Employee(String name,int salary,int sex){ 18 this.name=name; 19 this.salary=salary; 20 this.sex=sex; 21 } 22 /** 23 * 访问者访问内容 24 * @param iVisitor 25 */ 26 public abstract void accept(IVisitor iVisitor); 27 28 //set get 方法 29 public String getName() { 30 return name; 31 } 32 public void setName(String name) { 33 this.name = name; 34 } 35 public int getSalary() { 36 return salary; 37 } 38 public void setSalary(int salary) { 39 this.salary = salary; 40 } 41 public int getSex() { 42 return sex; 43 } 44 public void setSex(int sex) { 45 this.sex = sex; 46 } 47 48 }
【2】员工数据
1 package com.yeepay.sxf.template20; 2 /** 3 * 小兵(员工数据) 4 * @author sxf 5 * 6 */ 7 public class CommonEmployee extends Employee { 8 //工作内容 9 private String job; 10 //构造函数 11 public CommonEmployee(String name, int salary, int sex, String job) { 12 super(name, salary, sex); 13 this.job = job; 14 } 15 16 /** 17 * 访问者实现 18 */ 19 @Override 20 public void accept(IVisitor iVisitor) { 21 iVisitor.visit(this); 22 } 23 24 25 public String getJob() { 26 return job; 27 } 28 public void setJob(String job) { 29 this.job = job; 30 } 31 32 33 34 }
【3】管理层数据
1 package com.yeepay.sxf.template20; 2 /** 3 * 管理者 4 * @author sxf 5 * 6 */ 7 public class Manager extends Employee { 8 //业绩 9 private String refcestr; 10 11 //构造函数 12 public Manager(String name, int salary, int sex, String refcestr) { 13 super(name, salary, sex); 14 this.refcestr = refcestr; 15 } 16 17 /** 18 * 访问者访问 19 */ 20 @Override 21 public void accept(IVisitor iVisitor) { 22 iVisitor.visit(this); 23 } 24 25 26 public String getRefcestr() { 27 return refcestr; 28 } 29 30 public void setRefcestr(String refcestr) { 31 this.refcestr = refcestr; 32 } 33 34 35 }
【4】访问者接口
1 package com.yeepay.sxf.template20; 2 /** 3 * 访问者接口 4 * @author sxf 5 * 6 */ 7 public interface IVisitor { 8 //首先,定义我可以访问普通员工 9 public void visit(CommonEmployee commonEmployee); 10 //其次,定义我还可以访问部门经理 11 public void visit(Manager manager); 12 }
【5】普通展示访问者实现
1 package com.yeepay.sxf.template20; 2 /** 3 * 具体的访问者 4 * @author sxf 5 * 6 */ 7 public class Visitor implements IVisitor { 8 9 //访问普通员工,打印出报表 10 @Override 11 public void visit(CommonEmployee commonEmployee) { 12 System.out.println(this.getCommonEmployee(commonEmployee)); 13 } 14 15 @Override 16 public void visit(Manager manager) { 17 System.out.println(this.getManagerInfo(manager)); 18 } 19 //组装出基本信息 20 private String getBasicInfo(Employee employee){ 21 String info = "姓名:" + employee.getName() + "\t"; 22 info = info + "性别:" +(employee.getSex() == Employee.FEMALE?"女":"男"); 23 info = info + "薪水:" + employee.getSalary() + "\t"; 24 return info; 25 } 26 //组装出部门经理的信息 27 private String getManagerInfo(Manager manager){ 28 String basicInfo = this.getBasicInfo(manager); 29 String otherInfo = "业绩:"+manager.getRefcestr() + "\t"; 30 return basicInfo + otherInfo; 31 } 32 //组装出普通员工信息 33 private String getCommonEmployee(CommonEmployee commonEmployee){ 34 String basicInfo = this.getBasicInfo(commonEmployee); 35 String otherInfo = "工作:"+commonEmployee.getJob()+"\t"; 36 return basicInfo + otherInfo; 37 } 38 39 }
【6】报表展示访问者的扩展接口
1 package com.yeepay.sxf.template20; 2 /** 3 * 对访问者接口的扩展 4 * 展示访问者的报表 5 * @author sxf 6 * 7 */ 8 public interface IShowVisitor extends IVisitor{ 9 //展示报表 10 public void report(); 11 }
【7】统计访问者的扩展接口
1 package com.yeepay.sxf.template20; 2 /** 3 * 统计访问 4 * @author sxf 5 * 6 */ 7 public interface ITotalVisitor extends IVisitor { 8 9 //统计所有员工的工资总和 10 public void totalSalary(); 11 }
【8】报表展示访问者的扩展接口实现类
1 package com.yeepay.sxf.template20; 2 /** 3 * 展示的访问者 4 * @author sxf 5 * 6 */ 7 public class ShowVisitor implements IShowVisitor { 8 9 private String info=""; 10 11 //组装出基本信息 12 private String getBasicInfo(Employee employee){ 13 String info = "姓名:" + employee.getName() + "\t"; 14 info = info + "性别:" +(employee.getSex() == Employee.FEMALE?"女":"男"); 15 info = info + "薪水:" + employee.getSalary() + "\t"; 16 return info; 17 } 18 //组装出部门经理的信息 19 private String getManagerInfo(Manager manager){ 20 String basicInfo = this.getBasicInfo(manager); 21 String otherInfo = "业绩:"+manager.getRefcestr() + "\t"; 22 return basicInfo + otherInfo; 23 } 24 //组装出普通员工信息 25 private String getCommonEmployee(CommonEmployee commonEmployee){ 26 String basicInfo = this.getBasicInfo(commonEmployee); 27 String otherInfo = "工作:"+commonEmployee.getJob()+"\t"; 28 return basicInfo + otherInfo; 29 } 30 31 32 @Override 33 public void visit(CommonEmployee commonEmployee) { 34 this.info=this.info+this.getBasicInfo(commonEmployee); 35 } 36 37 @Override 38 public void visit(Manager manager) { 39 this.info=this.info+this.getManagerInfo(manager); 40 } 41 42 /** 43 * 展示 44 */ 45 @Override 46 public void report() { 47 System.out.println("ShowVisitor.report()"+this.info); 48 } 49 50 51 }
【9】统计访问者的扩展接口实现
1 package com.yeepay.sxf.template20; 2 /** 3 * 统计访问者接口实现 4 * @author sxf 5 * 6 */ 7 public class TotalVisitor implements ITotalVisitor{ 8 //管理层的工资系数 9 private final static int MANAGER_COEFFICIENT=5; 10 //员工的工资系数 11 private final static int COMMONEMPLOYEE_COEFFICIENT=2; 12 13 //普通员工工资总和 14 private int commonTotalSalary=0; 15 16 //部门经历的工资总和 17 private int managerTotalSalary=0; 18 19 @Override 20 public void visit(CommonEmployee commonEmployee) { 21 this.commonTotalSalary=this.commonTotalSalary+commonEmployee.getSalary()*COMMONEMPLOYEE_COEFFICIENT; 22 } 23 24 @Override 25 public void visit(Manager manager) { 26 this.commonTotalSalary=this.commonTotalSalary+manager.getSalary()*MANAGER_COEFFICIENT; 27 } 28 29 @Override 30 public void totalSalary() { 31 System.out.println("TotalVisitor.totalSalary(工资总和为===>:)"+(commonTotalSalary+managerTotalSalary)); 32 } 33 34 35 }
【10】测试类
1 package com.yeepay.sxf.template20; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 /** 6 * 访问者测试 7 * 双分派测试 8 * @author sxf 9 * 10 */ 11 public class ClientTest { 12 13 public static void main(String[] args) { 14 //test01(); 15 // test02(); 16 test03(); 17 } 18 19 20 //一个访问者测试 21 public static void test01(){ 22 //数据 23 List<Employee> list=mockEmployee(); 24 for(Employee mo:list){ 25 mo.accept(new Visitor()); 26 } 27 } 28 29 30 31 32 //多个访问者测试 33 public static void test02(){ 34 //数据 35 List<Employee> list=mockEmployee(); 36 37 //数据展示访问者 38 IShowVisitor showIVisitor=new ShowVisitor(); 39 //工资计算访问者 40 ITotalVisitor totalVisitor=new TotalVisitor(); 41 42 for(Employee employee:list){ 43 employee.accept(showIVisitor); 44 employee.accept(totalVisitor); 45 } 46 47 //数据展示 48 showIVisitor.report(); 49 //工资计算 50 totalVisitor.totalSalary(); 51 } 52 53 54 //制造员工集合 55 public static List<Employee> mockEmployee(){ 56 List<Employee> list=new ArrayList<Employee>(); 57 CommonEmployee bin1=new CommonEmployee("sxf", 15000, 1, "java开发"); 58 CommonEmployee bin2=new CommonEmployee("sxs", 15000, 1, ".net开发"); 59 CommonEmployee bin3=new CommonEmployee("sxy", 15000, 0, "前端开发"); 60 Manager manager1=new Manager("aaaaaaa", 100000, 0, "怕啊啊"); 61 Manager manager2=new Manager("bbbbbb", 100000, 0, "公官"); 62 list.add(bin1); 63 list.add(bin2); 64 list.add(bin3); 65 list.add(manager1); 66 list.add(manager2); 67 return list; 68 } 69 70 71 //测试双分派 72 public static void test03(){ 73 //重载,看参数类型 74 //重写,看调用者类型 75 AbsActor absActor=new OldActor(); 76 Role role=new KongFuRole(); 77 KongFuRole role2=new KongFuRole(); 78 absActor.act(role); 79 absActor.act(role2); 80 81 AbsActor absActor2=new YongActor(); 82 absActor2.act(role); 83 absActor2.act(role2); 84 } 85 }
【11】双分派
[1]角色
1 package com.yeepay.sxf.template20; 2 /** 3 * 角色 4 * @author sxf 5 * 6 */ 7 public interface Role { 8 //演员要扮演的角色 9 } 10 11 12 package com.yeepay.sxf.template20; 13 /** 14 * 功夫角色 15 * @author sxf 16 * 17 */ 18 public class KongFuRole implements Role{ 19 20 } 21 22 package com.yeepay.sxf.template20; 23 /** 24 * 白痴角色 25 * @author sxf 26 * 27 */ 28 public class IdiotRole implements Role{ 29 30 }
[2]演员
1 package com.yeepay.sxf.template20; 2 /** 3 * 抽象演员 4 * @author sxf 5 * 6 */ 7 public abstract class AbsActor { 8 9 public void act(Role role){ 10 System.out.println("AbsActor.act(演员可以演所有角色)"); 11 } 12 13 public void act(KongFuRole kongFuRole){ 14 System.out.println("AbsActor.act(演员可以演功夫角色)"); 15 } 16 } 17 18 19 package com.yeepay.sxf.template20; 20 /** 21 * 年龄大的演员 22 * @author sxf 23 * 24 */ 25 public class OldActor extends AbsActor{ 26 //不演功夫角色 27 @Override 28 public void act(KongFuRole kongFuRole){ 29 System.out.println("OldActor.act(年龄老的演员不能演功夫角色)"); 30 } 31 } 32 33 34 package com.yeepay.sxf.template20; 35 /** 36 * 年轻演员 37 * @author sxf 38 * 39 */ 40 public class YongActor extends AbsActor{ 41 42 //年轻演员最喜欢演功夫戏 43 public void act(KongFuRole kongFuRole){ 44 System.out.println("YongActor.act(年轻演员最喜欢演功夫角色)"); 45 } 46 }