第25章 访问者模式
25.1 员工的隐私何在
我们在前面讲过了组合模式和迭代器模式。通过组合模式能够把一个公司的人员组织机构树搭建起来,给管理带来非常大的便利,通过迭代器模式把每一个员工都遍历一遍,看看是不是 “有人去世了还在领退休金”,“拿高工资而不干活的尸位素餐”等情况,我们今天要做的就是把这些情况统计成一个报表呈报上去,让领导看看这种恶劣的情况有多严重。
我们公司有700名多技术人员,分布在全国各地,组织架构在组合模式中已经介绍过了,是很常见的家长领导型模式,每个技术人员的岗位都是固定的,你在组织机构的哪棵树下,充当的角色是什么,叶子节点都是非常明确的,每一个员工的信息(如名字、性别、薪水等)都是记录在数据库中,现在有这样一个需求,我要把公司中的所有人员信息都打印汇报上去。我们来看类图,如图25-1所示。
![](https://box.kancloud.cn/2016-08-14_57b00369ea922.jpg)
图25-1 员工信息类图
这个类图还是比较简单的,我们定义每个员工都有薪水salary、名称name、性别sex这3个属性,然后提供了一个抽象方法getOtherInfo由子类进行扩展,同时通过report方法打印出每一个员工的信息,这里使用模板方法模式。我们先来看一下抽象类,如代码清单25-1所示。
代码清单25-1 抽象员工
public abstract class Employee {
public final static int MALE = 0; //0代表是男性
public final static int FEMALE = 1; //1代表是女性
//甭管是谁,都有工资
private String name;
//只要是员工那就有薪水
private int salary;
//性别很重要
private int sex;
//以下是简单的getter/setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
//打印出员工的信息
public final void report(){
String info = "姓名:" + this.name + "\t";
info = info + "性别:" + (this.sex == FEMALE?"女":"男") + "\t";
info = info + "薪水:" + this.salary + "\t";
//获得员工的其他信息
info = info + this.getOtherInfo();
System.out.println(info);
}
//拼装员工的其他信息
protected abstract String getOtherInfo();
}
先看小兵的实现类,越卑微的人物越能引起共鸣,因为我们有共同的经历、思维和苦难。请看实现类,如代码清单25-2所示。
代码清单25-2 普通员工
public class CommonEmployee extends Employee {
//工作内容,这非常重要,以后的职业规划就是靠它了
private String job;
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
protected String getOtherInfo(){
return "工作:"+ this.job + "\t";
}
}
每个实现类都必须实现getOtherInfo信息,通过它获得用户个性信息,我们再来看管理阶层,如代码清单25-3所示。
代码清单25-3 管理阶层
public class Manager extends Employee {
//这类人物的职责非常明确:业绩
private String performance;
public String getPerformance() {
return performance;
}
public void setPerformance(String performance) {
this.performance = performance;
}
protected String getOtherInfo(){
return "业绩:"+ this.performance + "\t";
}
}
Performance这个单词在技术人员的眼里就代表性能,在实际商务英语中可以有Sales Performance(销售业绩)、performance evaluation(业绩评估)等。系统的框架都已经具备了,那我们来模拟一下这个过程,如代码清单25-4所示。
代码清单25-4 场景类
public class Client {
public static void main(String[] args) {
for(Employee emp:mockEmployee()){
emp.report();
}
}
//模拟出公司的人员情况,我们可以想象这个数据是通过持久层传递过来的
public static List<Employee> mockEmployee(){
List<Employee> empList = new ArrayList<Employee>();
//产生张三这个员工
CommonEmployee zhangSan = new CommonEmployee();
zhangSan.setJob("编写Java程序,绝对的蓝领、苦工加搬运工");
zhangSan.setName("张三");
zhangSan.setSalary(1800);
zhangSan.setSex(Employee.MALE);
empList.add(zhangSan);
//产生李四这个员工
CommonEmployee liSi = new CommonEmployee();
liSi.setJob("页面美工,审美素质太不流行了!");
liSi.setName("李四");
liSi.setSalary(1900);
liSi.setSex(Employee.FEMALE);
empList.add(liSi);
//再产生一个经理
Manager wangWu = new Manager();
wangWu.setName("王五");
wangWu.setPerformance("基本上是负值,但是我会拍马屁呀");
wangWu.setSalary(18750);
wangWu.setSex(Employee.MALE);
empList.add(wangWu);
return empList;
}
}
先通过mockEmployee来模拟出一个数组,初始化两个员工和一个经理,当然在实际项目中这个数组应该由持久层产生。运行结果如下所示:
| 姓名:张三 | 性别:男 | 薪水:1800 | 工作:编写Java程序,绝对的蓝领、苦工加搬运工 |
|-----|-----|-----|-----|
| 姓名:李四 | 性别:女 | 薪水:1900 | 工作:页面美工,审美素质太不流行了! |
| 姓名:王五 | 性别:男 | 薪水:18750 | 业绩:基本上是负值,但是我会拍马屁呀 |
结果出来了,非常正确。我们来想一想实际的情况,人力资源部门拿这份表格会给谁看呢?那当然是大老板了!大老板关心的是什么?关心部门经理的业绩!小兵的情况不是他要了解的,就像战争时期一位将军说:“我一想到我的士兵也有孩子、妻子、父母,我就痛心疾首……但是这是战场,我只能认为他们是一群机器……”是啊,其实我们也一样啊,那问题就出来了:
● 大老板就看部门经理的报表,小兵的报表可看可不看。
● 多个大老板的“嗜好”是不同的,主管销售的,则主要关心营销的情况;主管会计的,则主要关心企业的整体财务运行状态;主管技术的,则主要看技术的研发情况。
综合成一句话,这个报表会修改:数据的修改以及报表的展现修改,按照开闭原则,项目分析的时候已经考虑到这些可能引起变更的因素,就需要在设计时考虑通过扩展来避开未来需求变更而引起的代码修改风险。我们来想一想,每个普通员工类和经理类都用一个方法report(从父类继承过来的),他无法为每一个子类定制特殊的属性,简化类图如图25-2所示。
![](https://box.kancloud.cn/2016-08-14_57b0036a0db3d.jpg)
图25-2 简化类图
我们思考一下,如何提供一个能够为每个子类定制报表的方法呢?可以这样思考,普通员工和管理层员工是两个不同的对象,例如,我邀请一个人过来参观我的家,参观者参观完毕后分别进行描述,那参观的对象不同,描述的结果也当然不同。好,按照这思路,我们把方法report提取到另外一个类Visitor中来实现,如图25-3所示。
![](https://box.kancloud.cn/2016-08-14_57b0036a23c7b.jpg)
图25-3 改造后的简化类图
两个子类的report方法都不需要了,只有Visitor类来实现了report的方法,这个猛一看还真有点委托(intergration)的意味,我们实现出来你就知道这和委托有非常大的差距。详细类图如图25-4所示。
![](https://box.kancloud.cn/2016-08-14_57b0036a40a13.jpg)
图25-4 改造后的详细类图
在抽象类Employee中增加了accept方法,该方法是一个抽象方法,由子类实现,其意义就是说我这个类可以允许谁来访问,也就是定义一类访问者,在具体的实现类中调用访问者的方法。我们先看访问者接口IVisitor程序,如代码清单25-5所示。
代码清单25-5 访问者接口
public interface IVisitor {
//首先,定义我可以访问普通员工
public void visit(CommonEmployee commonEmployee);
//其次,定义我还可以访问部门经理
public void visit(Manager manager);
}
该接口的意义是:该接口可以访问两个对象,一个是普通员工,一个是高层员工。我们来看其具体实现类,如代码清单25-6所示。
代码清单25-6 访问者实现
public class Visitor implements IVisitor {
//访问普通员工,打印出报表
public void visit(CommonEmployee commonEmployee) {
System.out.println(this.getCommonEmployee(commonEmployee));
}
//访问部门经理,打印出报表
public void visit(Manager manager) {
System.out.println(this.getManagerInfo(manager));
}
//组装出基本信息
private String getBasicInfo(Employee employee){
String info = "姓名:" + employee.getName() + "\t";
info = info + "性别:" + (employee.getSex() == Employee.FEMALE?"女":"男") + "\t";
info = info + "薪水:" + employee.getSalary() + "\t";
return info;
}
//组装出部门经理的信息
private String getManagerInfo(Manager manager){
String basicInfo = this.getBasicInfo(manager);
String otherInfo = "业绩:"+manager.getPerformance() + "\t";
return basicInfo + otherInfo;
}
//组装出普通员工信息
private String getCommonEmployee(CommonEmployee commonEmployee){
String basicInfo = this.getBasicInfo(commonEmployee);
String otherInfo = "工作:"+commonEmployee.getJob()+"\t";
return basicInfo + otherInfo;
}
}
在具体的实现类中,定义了两个私有方法,作用就是产生需要打印的数据和格式,然后在访问者访问相关的对象时产生这个报表。抽象员工Employee稍有修改,如代码清单25-7所示。
代码清单25-7 抽象员工类
public abstract class Employee {
public final static int MALE = 0; //0代表是男性
public final static int FEMALE = 1; //1代表是女性
//甭管是谁,都有工资
private String name;
//只要是员工那就有薪水
private int salary;
//性别很重要
private int sex;
//以下是简单的getter/setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
//我允许一个访问者访问
public abstract void accept(IVisitor visitor);
}
抽象员工类有3个变动:
● 删除了report方法。
● 增加了accept方法,接受访问者的访问。
● 删除了getOtherInfo方法。它的实现由访问者来处理,因为访问者对被访问的对象是“心知肚明”的,非常了解被访问者。
我们继续来看员工实现类,普通员工代码清单25-8所示。
代码清单25-8 普通员工
public class CommonEmployee extends Employee {
//工作内容,这非常重要,以后的职业规划就是靠它了
private String job;
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
//我允许访问者访问
@Override
public void accept(IVisitor visitor){
visitor.visit(this);
}
}
上面是普通员工的实现类,该类的accept方法很简单,这个类就把自身传递过去,也就是让访问者访问本身这个对象。再看Manager类,如代码清单25-9所示。
代码清单25-9 管理层员工
public class Manager extends Employee {
//这类人物的职责非常明确:业绩
private String performance;
public String getPerformance() {
return performance;
}
public void setPerformance(String performance) {
this.performance = performance;
}
//部门经理允许访问者访问
@Override
public void accept(IVisitor visitor){
visitor.visit(this);
}
}
所有的业务定义都已经完成,我们来看看怎么模拟这个逻辑,如代码清单25-10所示。
代码清单25-10 场景类
public class Client {
public static void main(String[] args) {
for(Employee emp:mockEmployee()){
emp.accept(new Visitor());
}
}
//模拟出公司的人员情况,我们可以想象这个数据是通过持久层传递过来的
public static List<Employee> mockEmployee(){
List<Employee> empList = new ArrayList<Employee>();
//产生张三这个员工
CommonEmployee zhangSan = new CommonEmployee();
zhangSan.setJob("编写Java程序,绝对的蓝领、苦工加搬运工");
zhangSan.setName("张三");
zhangSan.setSalary(1800);
zhangSan.setSex(Employee.MALE);
empList.add(zhangSan);
//产生李四这个员工
CommonEmployee liSi = new CommonEmployee();
liSi.setJob("页面美工,审美素质太不流行了!");
liSi.setName("李四");
liSi.setSalary(1900);
liSi.setSex(Employee.FEMALE);
empList.add(liSi);
//再产生一个经理
Manager wangWu = new Manager();
wangWu.setName("王五");
wangWu.setPerformance("基本上是负值,但是我会拍马屁呀");
wangWu.setSalary(18750);
wangWu.setSex(Employee.MALE);
empList.add(wangWu);
return empList;
}
}
改动非常少,就黑体那么一行的改动,运行结果如下:
| 姓名:张三 | 性别:男 | 薪水:1800 | 工作:编写Java程序,绝对的蓝领、苦工加搬运工 |
|-----|-----|-----|-----|
| 姓名:李四 | 性别:女 | 薪水:1900 | 工作:页面美工,审美素质太不流行了! |
| 姓名:王五 | 性别:男 | 薪水:18750 | 业绩:基本上是负值,但是我会拍马屁呀 |
运行结果也完全相同,那回过头来看看这个程序是怎么实现的:
● 第一,通过循环遍历所有元素。
● 第二,每个员工对象都定义了一个访问者。
● 第三,员工对象把自己作为一个参数调用访问者visit方法。
● 第四,访问者调用自己内部的计算逻辑,计算出相应的数据和表格元素。
● 第五,访问者打印出报表和数据。
事情的经过就是这个样子。那我们再来看看上面提到的数据和报表格式都会改变的情况。首先是数据的改变,数据改了当然都要改,说不上两个方案有什么优劣;其次是报表格式的修改,这个方案绝对是有优势的,我只要再产生一个IVisitor的实现类就可以产生一个新的报表格式,而其他的类都不用修改,如果你用Spring开发,那就更好了,在Spring的配置文件中使用的是接口注入,我只要把配置文件中的 ref修改一下就行了,其他的都不用修改了!这就是访问者模式的优势所在。
- 前言
- 第一部分 大旗不挥,谁敢冲锋——6大设计原则全新解读
- 第1章 单一职责原则
- 1.2 绝杀技,打破你的传统思维
- 1.3 我单纯,所以我快乐
- 1.4 最佳实践
- 第2章 里氏替换原则
- 2.2 纠纷不断,规则压制
- 2.3 最佳实践
- 第3章 依赖倒置原则
- 3.2 言而无信,你太需要契约
- 3.3 依赖的三种写法
- 3.4 最佳实践
- 第4章 接口隔离原则
- 4.2 美女何其多,观点各不同
- 4.3 保证接口的纯洁性
- 4.4 最佳实践
- 第5章 迪米特法则
- 5.2 我的知识你知道得越少越好
- 5.3 最佳实践
- 第6章 开闭原则
- 6.2 开闭原则的庐山真面目
- 6.3 为什么要采用开闭原则
- 6.4 如何使用开闭原则
- 6.5 最佳实践
- 第二部分 真刀实枪 ——23种设计模式完美演绎
- 第7章 单例模式
- 7.2 单例模式的定义
- 7.3 单例模式的应用
- 7.4 单例模式的扩展
- 7.5 最佳实践
- 第8章 工厂方法模式
- 8.2 工厂方法模式的定义
- 8.3 工厂方法模式的应用
- 8.4 工厂方法模式的扩展
- 8.5 最佳实践
- 第9章 抽象工厂模式
- 9.2 抽象工厂模式的定义
- 9.3 抽象工厂模式的应用
- 9.4 最佳实践
- 第10章 模板方法模式
- 10.2 模板方法模式的定义
- 10.3 模板方法模式的应用
- 10.4 模板方法模式的扩展
- 10.5 最佳实践
- 第11章 建造者模式
- 11.2 建造者模式的定义
- 11.3 建造者模式的应用
- 11.4 建造者模式的扩展
- 11.5 最佳实践
- 第12章 代理模式
- 12.2 代理模式的定义
- 12.3 代理模式的应用
- 12.4 代理模式的扩展
- 12.5 最佳实践
- 第13章 原型模式
- 13.2 原型模式的定义
- 13.3 原型模式的应用
- 13.4 原型模式的注意事项
- 13.5 最佳实践
- 第14章 中介者模式
- 14.2 中介者模式的定义
- 14.3 中介者模式的应用
- 14.4 中介者模式的实际应用
- 14.5 最佳实践
- 第15章 命令模式
- 15.2 命令模式的定义
- 15.3 命令模式的应用
- 15.4 命令模式的扩展
- 15.5 最佳实践
- 第16章 责任链模式
- 16.2 责任链模式的定义
- 16.3 责任链模式的应用
- 16.4 最佳实践
- 第17章 装饰模式
- 17.2 装饰模式的定义
- 17.3 装饰模式应用
- 17.4 最佳实践
- 第18章 策略模式
- 18.2 策略模式的定义
- 18.3 策略模式的应用
- 18.4 策略模式的扩展
- 18.5 最佳实践
- 第19章 适配器模式
- 19.2 适配器模式的定义
- 19.3 适配器模式的应用
- 19.4 适配器模式的扩展
- 19.5 最佳实践
- 第20章 迭代器模式
- 20.2 迭代器模式的定义
- 20.3 迭代器模式的应用
- 20.4 最佳实践
- 第21章 组合模式
- 21.2 组合模式的定义
- 21.3 组合模式的应用
- 21.4 组合模式的扩展
- 21.5 最佳实践
- 第22章 观察者模式
- 22.2 观察者模式的定义
- 22.3 观察者模式的应用
- 22.4 观察者模式的扩展
- 22.5 最佳实践
- 第23章 门面模式
- 23.2 门面模式的定义
- 23.3 门面模式的应用
- 23.4 门面模式的注意事项
- 23.5 最佳实践
- 第24章 备忘录模式
- 24.2 备忘录模式的定义
- 24.3 备忘录模式的应用
- 24.4 备忘录模式的扩展
- 24.5 最佳实践
- 第25章 访问者模式
- 25.2 访问者模式的定义
- 25.3 访问者模式的应用
- 25.4 访问者模式的扩展
- 25.5 最佳实践
- 第26章 状态模式
- 26.2 状态模式的定义
- 26.3 状态模式的应用
- 第27章 解释器模式
- 27.2 解释器模式的定义
- 27.3 解释器模式的应用
- 27.4 最佳实践
- 第28章 享元模式
- 28.2 享元模式的定义
- 28.3 享元模式的应用
- 28.4 享元模式的扩展
- 28.5 最佳实践
- 第29章 桥梁模式
- 29.2 桥梁模式的定义
- 29.3 桥梁模式的应用
- 29.4 最佳实践
- 第三部分 谁的地盘谁做主 ——设计模式PK
- 第30章 创建类模式大PK
- 30.1 工厂方法模式VS建造者模式
- 30.2 抽象工厂模式VS建造者模式
- 第31章 结构类模式大PK
- 31.1 代理模式VS装饰模式
- 31.2 装饰模式VS适配器模式
- 第32章 行为类模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS状态模式
- 32.3 观察者模式VS责任链模式
- 第33章 跨战区PK
- 33.1 策略模式VS桥梁模式
- 33.2 门面模式VS中介者模式
- 33.3 包装模式群PK
- 第四部分 完美世界 ——设计模式混编
- 第34章 命令模式+责任链模式
- 34.2 混编小结
- 第35章 工厂方法模式+策略模式
- 35.2 混编小结
- 第36章 观察者模式+中介者模式
- 36.2 混编小结
- 第五部分 扩展篇
- 第37章 MVC框架
- 37.2 最佳实践
- 第38章 新模式
- 38.1 规格模式
- 38.2 对象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空对象模式
- 附录 23种设计模式彩图