Java设计模式——对象行为模式——访问者模式(VISITOR)
定义
封装一些作用域某种数据结构中的个元素的操作,在不改变这个数据结构的
前提下,定义作用于这些元素的新的操作。
一、概述
访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为“对象结构”,访问者通过遍历对象结构实现对其中存储的元素的逐个操作。访问者模式是一种对象行为型模式。
二、适用场景
当有多种类型的访问者(或是操作者) 对一组被访问者对象集合(或是对象结构)进行操作(其中对象集合也包含多种类型对象),不同的访问者类型对每一种具体的被访问者对象提供不同的访问操作,每种访问者类型对象对不同的被访问者也有不同的访问操作,那么这种场景就非常适用访问者模式。
如果前面这几句比较绕的文字说明没看明白,那么小吕就举例一个生活中的业务场景:
你所在的公司每个月人力资源部要对所有员工进行上班时长、加班时长统计,而财务部要对所有员工进行工资核算,不同职位的员工薪资核算标准肯定不一样啊,这个大家都明白。在这个案例中人力资源部和财务部是两个不同类型的部门(访问者),所有员工(被访问者)是一个对象集合,而员工又划分为管理者和技术者两种类型(备注:这里小吕只是简单划分为两类),在每月的统计中,人力资源部需要分别对员工进行上班时长和加班时长进行统计,而财务部需要对不同职位的员工进行薪资核算,可见不同部门职责不同,及对员工的访问操作不同、每个部门对不同类型的员工的访问操作也不同。那么针对这种场景 我们有必要了解一下访问者模式。
三、UML类图
四、参与者
1>、Visitor(抽象访问者):为每种具体的被访问者(ConcreteElement)声明一个访问操作;
2>、ConcreteVisitor(具体访问者):实现对被访问者(ConcreteElement)的具体访问操作;
3>、Element(抽象被访问者):通常有一个Accept方法,用来接收/引用一个抽象访问者对象;
4>、ConcreteElement(具体被访问者对象):实现Accept抽象方法,通过传入的具体访问者参数、调用具体访问者对该对象的访问操作方法实现访问逻辑;
5>、Clent、ObjectStructure(客户端访问过程测试环境):该过程中,被访问者通常为一个集合对象,通过对集合的遍历完成访问者对每一个被访问元素的访问操作;
/** * 公司部门(访问者)抽象类 */ public abstract class Department { // 声明一组重载的访问方法,用于访问不同类型的具体元素(这里指的是不同的员工) /** * 抽象方法 访问公司管理者对象<br/> * 具体访问对象的什么 就由具体的访问者子类(这里指的是不同的具体部门)去实现 * @param me */ public abstract void visit(ManagerEmployee me); /** * 抽象方法 访问公司普通员工对象<br/> * 具体访问对象的什么 就由具体的访问者子类(这里指的是不同的具体部门)去实现 * @param ge */ public abstract void visit(GeneralEmployee ge); }
/** * 具体访问者对象:公司财务部<br/> * 财务部的职责就是负责统计核算员工的工资 * */ public class FADepartment extends Department { /** * 访问公司管理者对象的每月工资 */ @Override public void visit(ManagerEmployee me) { double totalWage = me.getTotalWage(); System.out.println("管理者: " + me.getName() + " 固定工资 =" + me.getWage() + ", 迟到时长 " + me.getPunishmentTime() + "小时"+ ", 实发工资="+totalWage); } /** * 访问公司普通员工对象的每月工资 */ @Override public void visit(GeneralEmployee ge) { double totalWage = ge.getTotalWage(); System.out.println("普通员工: " + ge.getName() + " 固定工资 =" + ge.getWage() + ", 迟到时长 " + ge.getPunishmentTime() + "小时"+ ", 实发工资="+totalWage); } }
/** * 具体访问者对象:公司人力资源部<br/> * 人力资源部的职责就是负责统计核算员工的每月上班时长 */ public class HRDepartment extends Department { /** * 访问公司管理者对象的每月实际上班时长统计 */ @Override public void visit(ManagerEmployee me) { me.getTotalTimeSheet(); } /** * 访问公司普通员工对象的每月实际上班时长统计 */ @Override public void visit(GeneralEmployee ge) { ge.getTotalTimeSheet(); } }
/** * 公司员工(被访问者)抽象类 */ public abstract class Employee { /** * 接收/引用一个抽象访问者对象 * @param department 抽象访问者 这里指的是公司部门如 人力资源部、财务部 */ public abstract void accept(Department department); }
/** * 公司普通员工(具体的被访问者对象) */ public class GeneralEmployee extends Employee { // 员工姓名 private String name; // 每天上班时长 private int timeSheet; // 每月工资 private double wage; // 请假/迟到 惩罚时长 private int punishmentTime; public GeneralEmployee(String name, int timeSheet, double wage, int punishmentTime) { this.name = name; this.timeSheet = timeSheet; this.wage = wage; this.punishmentTime = punishmentTime; } @Override public void accept(Department department) { department.visit(this); } /** * 获取每月的上班实际时长 = 每天上班时长 * 每月上班天数 - 惩罚时长 * @return */ public int getTotalTimeSheet() { return timeSheet * 22 - punishmentTime; } /** * 获取每月实际应发工资 = 每月固定工资 - 惩罚时长 * 10<br/> * <作为公司普通员工 每迟到1小时 扣10块钱 坑吧? 哈哈> * * @return */ public double getTotalWage() { return wage - punishmentTime * 10; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getWage() { return wage; } public void setWage(double wage) { this.wage = wage; } public int getPunishmentTime() { return punishmentTime; } public void setPunishmentTime(int punishmentTime) { this.punishmentTime = punishmentTime; } }
/** * 公司员工:管理者(具体的被访问者对象) * */ public class ManagerEmployee extends Employee { // 员工姓名 private String name; // 每天上班时长 private int timeSheet; // 每月工资 private double wage; // 请假/迟到 惩罚时长 private int punishmentTime; public ManagerEmployee(String name, int timeSheet, double wage, int punishmentTime) { this.name = name; this.timeSheet = timeSheet; this.wage = wage; this.punishmentTime = punishmentTime; } @Override public void accept(Department department) { department.visit(this); } /** * 获取每月的上班实际时长 = 每天上班时长 * 每月上班天数 - 惩罚时长 * @return */ public int getTotalTimeSheet(){ return timeSheet * 22 - punishmentTime; } /** * 获取每月实际应发工资 = 每月固定工资 - 惩罚时长 * 5<br/> * <作为公司管理者 每迟到1小时 扣5块钱> * @return */ public double getTotalWage(){ return wage - punishmentTime * 5; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getWage() { return wage; } public void setWage(double wage) { this.wage = wage; } public int getPunishmentTime() { return punishmentTime; } public void setPunishmentTime(int punishmentTime) { this.punishmentTime = punishmentTime; } }
package sheji; import java.util.ArrayList; import java.util.List; public class Client { public static void main(String[] args) { List<Employee> employeeList = new ArrayList<Employee>(); Employee mep1,mep2,gep1,gep2,gep3; // 管理者1 mep1 = new ManagerEmployee("王总", 8, 20000, 10); // 管理者2 mep2 = new ManagerEmployee("谢经理", 8, 15000, 15); // 普通员工1 gep1 = new GeneralEmployee("小杰", 8, 8000, 8); // 普通员工2 gep2 = new GeneralEmployee("小晓", 8, 8500, 12); // 普通员工3 gep3 = new GeneralEmployee("小虎", 8, 7500, 0); employeeList.add(mep1); employeeList.add(mep2); employeeList.add(gep1); employeeList.add(gep2); employeeList.add(gep3); // 财务部 对公司员工的工资核算/访问 FADepartment department = new FADepartment(); for(Employee employee : employeeList){ employee.accept(department); } } }管理者: 王总 固定工资 =20000.0, 迟到时长 10小时, 实发工资=19950.0
管理者: 谢经理 固定工资 =15000.0, 迟到时长 15小时, 实发工资=14925.0
普通员工: 小杰 固定工资 =8000.0, 迟到时长 8小时, 实发工资=7920.0
普通员工: 小晓 固定工资 =8500.0, 迟到时长 12小时, 实发工资=8380.0
普通员工: 小虎 固定工资 =7500.0, 迟到时长 0小时, 实发工资=7500.0
使用场景
- 定义复杂的类很少做修改,但经常需要向其添加新的操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让
这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中。
优缺点
优点
- 1.好的扩展性,能在不修改结构元素情况下,为元素添加新功能;
- 2.好的复用性,通过访问者来定义整个对象结构统一的功能,从而提高复用程度;
- 3.分离无关行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
缺点
- 1.不适用于结构中类经常变化的情况
- 2.破坏封装性,具体元素对访问者公布细节