场景
访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,
这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。
例如处方单中的各种药品信息就是被访问的元素,而划价人员和药房工作人员就是访问者。
我们可以将处方单看成一个药品信息的集合,里面包含了一种或多种不同类型的药品信息,
不同类型的工作人员(如划价人员和药房工作人员)在操作同一个药品信息集合时将提供不同的处理方式,
而且可能还会增加新类型的工作人员来操作处方单。
访问者模式使得用户可以在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作。
在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为“对象结构”,
访问者通过遍历对象结构实现对其中存储的元素的逐个操作。
访问者模式(Visitor Pattern):提供一个作用于某对象结构中的各元素的操作表示,
它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。
访问者模式的结构较为复杂,其结构如图
在访问者模式结构图中包含如下几个角色:
●Vistor(抽象访问者):
抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,
从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。
●ConcreteVisitor(具体访问者):
具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
●Element(抽象元素):
抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。
●ConcreteElement(具体元素):
具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。
● ObjectStructure(对象结构):
对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。
它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。
注:
实现
1、示例场景
软件公司欲为某银行开发一套OA系统,在该OA系统中包含一个员工信息管理子系统,该银行员工包括正式员工和临时工,
每周人力资源部和财务部等部门需要对员工数据进行汇总,汇总数据包括员工工作时间、员工工资等。
该公司基本制度如下:
(1) 正式员工(Full time Employee)每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同;
如果超过40小时,超出部分按照100元/小时作为加班费;
如果少于40小时,所缺时间按照请假处理,请假所扣工资以80元/小时计算,直到基本工资扣除到零为止。
除了记录实际工作时间外,人力资源部需记录加班时长或请假时长,作为员工平时表现的一项依据。
(2) 临时工(Part time Employee)每周工作时间不固定,基本工资按小时计算,不同岗位的临时工小时工资不同。
人力资源部只需记录实际工作时间。人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,
人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。
开发人员针对上述需求,使用访问者模式实现,使得系统可以很方便地增加新类型的访问者,更加符合“单一职责原则”和“开闭原则”。
2、设计类图如下
FADepartment表示财务部,HRDepartment表示人力资源部,它们充当具体访问者角色,其抽象父类Department充当抽象访问者角色;
EmployeeList充当对象结构,用于存储员工列表;FulltimeEmployee表示正式员工,ParttimeEmployee表示临时工,
它们充当具体元素角色,其父接口Employee充当抽象元素角色。
3、新建员工类:抽象元素类
interface Employee {
//接受一个抽象访问者访问
public void accept(Department handler);
}
4、新建全职员工类:具体元素类
/**
* 全职员工类:具体元素类
*/
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 name;
}
public void setName(String name) {
this.name = name;
}
public double getWeeklyWage() {
return weeklyWage;
}
public void setWeeklyWage(double weeklyWage) {
this.weeklyWage = weeklyWage;
}
public int getWorkTime() {
return workTime;
}
public void setWorkTime(int workTime) {
this.workTime = workTime;
}
@Override
public void accept(Department handler) {
handler.visit(this);
}
}
5、新建兼职员工类:具体元素类
//兼职员工类:具体元素类
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 name;
}
public void setName(String name) {
this.name = name;
}
public double getHourwage() {
return hourwage;
}
public void setHourwage(double hourwage) {
this.hourwage = hourwage;
}
public int getWorkTime() {
return workTime;
}
public void setWorkTime(int workTime) {
this.workTime = workTime;
}
@Override
public void accept(Department handler) {
//调用访问者的访问方法
handler.visit(this);
}
}
6、新建部门类:抽象访问者类
/**
* 部门类:抽象访问者
*/
abstract class Department {
//声明一组重载的方法,用于访问不用类型的具体元素
public abstract void visit(FulltimeEmployee employee);
public abstract void visit(ParttimeEmployee employee);
}
7、新建财务部类:具体访问者类
//财务部类:具体访问者类
public class FADepartment extends Department{
//实现财务部对全职员工的访问
@Override
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);
}
//实现财务部对兼职员工的访问
@Override
public void visit(ParttimeEmployee employee) {
int workTime = employee.getWorkTime();
double hourWage = employee.getHourwage();
System.out.println("临时工"+employee.getName()+"实际工资为:"+workTime * hourWage);
}
}
8、新建人力资源部类:具体访问者类
//人力资源部类:具体访问者类
public class HRDepartment extends Department{
//实现人力资源对全职员工的访问
@Override
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));
}
}
//实现人力资源部对兼职员工的访问
@Override
public void visit(ParttimeEmployee employee) {
int workTime = employee.getWorkTime();
System.out.println("临时工"+employee.getName()+"实际工作时间:"+workTime);
}
}
9、新建员工列表类:对象结构
import java.util.ArrayList;
//员工类别类:对象结构
public class EmployeeList {
//定义一个集合用于存储员工对象
private ArrayList<Employee> list = new ArrayList<>();
public void addEmployee(Employee employee){
list.add(employee);
}
//遍历访问员工集合中的每一个员工对象
public void accept(Department handler){
for(Object obj:list){
((Employee)obj).accept(handler);
}
}
}
10、编写客户端测试代码
public class Client {
public static void main(String[] args) {
EmployeeList list = new EmployeeList(){{
this.addEmployee(new FulltimeEmployee("张三",1200,40));
this.addEmployee(new FulltimeEmployee("李四",1500,30));
this.addEmployee(new FulltimeEmployee("王五",2000,50));
this.addEmployee(new ParttimeEmployee("赵六",50,10));
this.addEmployee(new ParttimeEmployee("周七",90,20));
}};
//实现财务部门的访问
System.out.println("实现财务部门的访问");
Department dep;
dep = new FADepartment();
list.accept(dep);
//实现人力部门的访问
System.out.println("实现人力部门的访问");
dep = new HRDepartment();
list.accept(dep);
}
}
11、运行结果
如果要在系统中增加一种新的访问者,无须修改源代码,只要增加一个新的具体访问者类即可,
在该具体访问者中封装了新的操作元素对象的方法。从增加新的访问者的角度来看,访问者模式符合“开闭原则”。
如果要在系统中增加一种新的具体元素,例如增加一种新的员工类型为“退休人员”,
由于原有系统并未提供相应的访问接口(在抽象访问者中没有声明任何访问“退休人员”的方法),
因此必须对原有系统进行修改,在原有的抽象访问者类和具体访问者类中增加相应的访问方法。从增加新的元素的角度来看,
访问者模式违背了“开闭原则”。
综上所述,访问者模式与抽象工厂模式类似,对“开闭原则”的支持具有倾斜性,可以很方便地添加新的访问者,但是添加新的元素较为麻烦。