访问者模式
访问者模式概述
分析:
处方单:
药品信息的集合,包含一种或多种不同类型的药品信息
不同类型的工作人员(例如划价人员和药房工作人员)在操作同一个药品信息集合时将提供不同的处理方式
可能会增加新类型的工作人员来操作处方单
软件开发:
处方单-->对象结构
药品信息-->元素
工作人员-->访问者
- 对象结构中存储了多种不同类型的对象信息
- 对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式
- 还有可能需要增加新的处理方式
访问者模式的定义
访问者模式:
表示一个作用于某对象结构中的各个元素的操作。
访问者模式让你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
- 它为操作存储不同类型元素的对象结构提供了一种解决方案
- 用户可以对不同类型的元素施加不同的操作
访问者模式的对象
访问者模式包含以下5个角色:
Visitor(抽象访问者)
ConcreteVisitor(具体访问者)
Element(抽象元素)
ConcreteElement(具体元素)
ObjectStructure(对象结构)
访问者模式结构与实现
双重分派机制是访问者模式的关键。它允许:
- 对象结构把请求分派给适当的元素。
- 元素再把请求分派给适当的访问者。
双重分派机制
(1) 调用具体元素类的accept(Visitor visitor)
方法(接收访问者),并将Visitor子类对象
作为其参数
(2) 在具体元素类accept(Visitor visitor)
方法内部调用传入的Visitor对象
的visit()
方法(访问者的参观方法),例如visit(ConcreteElementA elementA)
,将当前具体元素类对象(this
)作为参数,例如visitor.visit(this)
(3) 执行Visitor
对象的visit()
方法(访问者的参观方法),在其中还可以调用具体元素对象的业务方法
通过实例学习
某公司OA系统中包含一个员工信息管理子系统,该公司员工包括正式员工和临时工,每周人力资源部和财务部等部门需要对员工数据进行汇总,汇总数据包括员工工作时间、员工工资等。该公司基本制度如下:
(1) 正式员工每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同。如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假所扣工资以80元/小时计算,直到基本工资扣除到零为止。除了记录实际工作时间外,人力资源部需记录加班时长或请假时长,作为员工平时表现的一项依据。
(2) 临时工每周工作时间不固定,基本工资按小时计算,不同岗位的临时工小时工资不同。人力资源部只需记录实际工作时间。
人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。
现使用访问者模式设计该系统,绘制类图并编程模拟实现。
抽象元素类
// 为什么使用抽象元素类: 一个对象结构中可能有不同的元素类,这里定义一个共有方法即可。
// 既然是元素类,就是用来被访问者访问的,所以定义一个接收方法
//员工类:抽象元素类
public interface Employee {
public void accept(Department handler); //接受一个抽象访问者访问
}
具体元素类
// 构造函数传入实例的基本属性
// 实现接口的方法,在方法里调用访问者的访问方法。
//全职员工类:具体元素类
public class FulltimeEmployee implements Employee {
private String name; //员工姓名
private double weeklyWage; //员工周薪
private int workTime; //工作时间
public FulltimeEmployee(String name,double weeklyWage,int workTime) {
this.name = name;
this.weeklyWage = weeklyWage;
this.workTime = workTime;
}
public String getName() {
return (this.name);
}
public double getWeeklyWage() {
return (this.weeklyWage);
}
public int getWorkTime() {
return (this.workTime);
}
public void accept(Department handler) {
handler.visit(this); //调用访问者的访问方法
}
}
//兼职员工类:具体元素类
public class ParttimeEmployee implements Employee {
private String name; //员工姓名
private double hourWage; //员工时薪
private int workTime; //工作时间
public ParttimeEmployee(String name,double hourWage,int workTime) {
this.name = name;
this.hourWage = hourWage;
this.workTime = workTime;
}
public String getName() {
return (this.name);
}
public double getHourWage() {
return (this.hourWage);
}
public int getWorkTime() {
return (this.workTime);
}
public void accept(Department handler) {
handler.visit(this); //调用访问者的访问方法
}
}
抽象访问者
// 访问者也多种多样,因此提取公共部分,将其抽象出来
// 它们都要去访问具体元素类,但是不同元素类的访问方法不同,因此定义两个方法,分别接收不同的对象
// 这里定义的是重载方法,这样访问不同类型的元素,只需要参数不一样即可
//部门类:抽象访问者类
public abstract class Department {
//声明一组重载的访问方法,用于访问不同类型的具体元素
public abstract void visit(FulltimeEmployee employee);
public abstract void visit(ParttimeEmployee employee);
}
具体访问者
// 具体访问者肯定是要重写访问方法的
// 具体实现里书写基本的逻辑代码,所以需要获得元素对象的属性,因此传入元素对象是一个好的决定
//财务部类:具体访问者类
public class FADepartment extends Department {
//实现财务部对全职员工的访问
public void visit(FulltimeEmployee employee) {
int workTime = employee.getWorkTime();
double weekWage = employee.getWeeklyWage();
if(workTime > 40) {
weekWage = weekWage + (workTime - 40) * 100;
}
else if(workTime < 40) {
weekWage = weekWage - (40 - workTime) * 80;
if(weekWage < 0) {
weekWage = 0;
}
}
System.out.println("正式员工" + employee.getName() + "实际工资为:" + weekWage + "元。");
}
//实现财务部对兼职员工的访问
public void visit(ParttimeEmployee employee) {
int workTime = employee.getWorkTime();
double hourWage = employee.getHourWage();
System.out.println("临时工" + employee.getName() + "实际工资为:" + workTime * hourWage + "元。");
}
}
//人力资源部类:具体访问者类
public class HRDepartment extends Department {
//实现人力资源部对全职员工的访问
public void visit(FulltimeEmployee employee) {
int workTime = employee.getWorkTime();
System.out.println("正式员工" + employee.getName() + "实际工作时间为:" + workTime + "小时。");
if(workTime > 40) {
System.out.println("正式员工" + employee.getName() + "加班时间为:" + (workTime - 40) + "小时。");
}
else if(workTime < 40) {
System.out.println("正式员工" + employee.getName() + "请假时间为:" + (40 - workTime) + "小时。");
}
}
//实现人力资源部对兼职员工的访问
public void visit(ParttimeEmployee employee) {
int workTime = employee.getWorkTime();
System.out.println("临时工" + employee.getName() + "实际工作时间为:" + workTime + "小时。");
}
}
对象结构类
// 对象结构类拥有一个或多个元素。这些元素可以是不同类型的对象,组成一个整体。
// 对象结构类提供 Accept() 接口,用于接受访问者。在 Accept() 中,对象结构类将调用访问者的相应元素的方法,实现对元素的访问。
// 当对象结构变化时,只需要更新对象结构类,不影响访问者类。
//员工列表类:对象结构
public class EmployeeList {
//定义一个集合用于存储员工对象
private ArrayList<Employee> list = new ArrayList<Employee>();
public void addEmployee(Employee employee) {
list.add(employee);
}
//遍历访问员工集合中的每一个员工对象
public void accept(Department handler) {
for(Object obj : list) {
((Employee)obj).accept(handler);
}
}
}
调用类
public class Client {
public static void main(String args[]) {
EmployeeList list = new EmployeeList();
Employee fte1,fte2,fte3,pte1,pte2;
fte1 = new FulltimeEmployee("张无忌",3200.00,45);
fte2 = new FulltimeEmployee("杨过",2000.00,40);
fte3 = new FulltimeEmployee("段誉",2400.00,38);
pte1 = new ParttimeEmployee("洪七公",80.00,20);
pte2 = new ParttimeEmployee("郭靖",60.00,18);
list.addEmployee(fte1);
list.addEmployee(fte2);
list.addEmployee(fte3);
list.addEmployee(pte1);
list.addEmployee(pte2);
Department dep;
dep = new FADepartment();
list.accept(dep);
}
}
结果及分析
正式员工张无忌实际工资为:3700.0元。
正式员工杨过实际工资为:2000.0元。
正式员工段誉实际工资为:2240.0元。
临时工洪七公实际工资为:1600.0元。
临时工郭靖实际工资为:1080.0元。
- 定义多个员工(具体元素类),将其加入到员工列表类(对象结构类)中
- 然后呢,调用对象结构类的接收访问者的方法,具体为遍历员工列表类,每个员工(具体元素类)都去接收访问者
- 此时将访问者对象传入具体元素类,具体元素类再去调用访问者的访问方法,并把自己作为对象传入访问者。
这个模式真的挺难的,细节也很多。
- 访问类区分具体元素的类型是通过参数,所以要重载
visit
方法 - 对象结构类只用遍历元素,里面调用的方法为存储的具体元素类的
accept
方法, 这样做有利于- 符合开闭原则。如果需要新增元素类型,只需要修改对象结构类,无需修改访问者类。
- 符合单一职责原则。对象结构类只负责管理和遍历元素,不负责访问元素。访问元素的操作延迟到了访问者类中
- 解耦了元素类和访问者类。
- 具体元素类中需要去调用访问者的
visit
方法,这样访问者的适配性很强,只用实现不同元素的不同重载了。
实例类图
(1) Employee:员工类,充当抽象元素类
(2) FulltimeEmployee:全职员工类,充当具体元素类
(3) ParttimeEmployee:兼职员工类,充当具体元素类
(4) Department:部门类,充当抽象访问者类
(5) FADepartment:财务部类,充当具体访问者类
(6) HRDepartment:人力资源部类,充当具体访问者类
(7) EmployeeList:员工列表类,充当对象结构
(8) Client:客户端测试类
模式优缺点
模式优点
-
增加新的访问操作很方便(只用实现visit方法即可)
-
将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中,类的职责更加清晰(不然的话,就必须在元素类里判断访问者对象,然后元素类做相应的改进;因此,在元素类中调用访问者的访问方法,在具体访问者类中完成元素类的修改)
-
让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作
模式缺点
- 增加新的元素类很困难(加新的访问者类就必须多写一个重载)
- 破坏了对象的封装性(访问者类需要直接访问和修改元素类的内部状态,这破坏了元素类的封装性。)
适用环境
- 一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作
- 需要对一个对象结构中的对象进行很多不同的且不相关的操作,并需要避免让这些操作*“污染”*这些对象的类,也不希望在增加新操作时修改这些类
- 对象结构中对象对应的类很少改变(指不增加元素类了),但经常需要在此对象结构上定义新的操作