JAVA设计模式之 訪问者模式【Visitor Pattern】

一、概述

    訪问者模式是一种较为复杂的行为型设计模式,它包括訪问者和被訪问元素两个主要组成部分。这些被訪问的元素通常具有不同的类型,且不同的訪问者能够对它们进行不同的訪问操作。在使用訪问者模式时,被訪问元素通常不是单独存在的,它们存储在一个集合中。这个集合被称为“对象结构”,訪问者通过遍历对象结构实现对当中存储的元素的逐个操作。訪问者模式是一种对象行为型模式。


二、适用场景

    当有多种类型的訪问者(或是操作者) 对一组被訪问者对象集合(或是对象结构)进行操作(当中对象集合也包括多种类型对象),不同的訪问者类型对每一种详细的被訪问者对象提供不同的訪问操作,每种訪问者类型对象对不同的被訪问者也有不同的訪问操作。那么这样的场景就很适用訪问者模式。

    假设前面这几句比較绕的文字说明没看明确。那么小吕就举例一个生活中的业务场景:

    你所在的公司每一个月人力资源部要对全部员工进行上班时长、加班时长统计。而財务部要对全部员工进行工资核算。不同职位的员工薪资核算标准肯定不一样啊,这个大家都明确。在这个案例中人力资源部和財务部是两个不同类型的部门(訪问者),全部员工(被訪问者)是一个对象集合,而员工又划分为管理者和技术者两种类型(备注:这里小吕仅仅是简单划分为两类),在每月的统计中,人力资源部须要分别对员工进行上班时长和加班时长进行统计。而財务部须要对不同职位的员工进行薪资核算,可见不同部门职责不同,及对员工的訪问操作不同、每一个部门对不同类型的员工的訪问操作也不同。

那么针对这样的场景  我们有必要了解一下訪问者模式。


三、UML类图


四、參与者

1>、Visitor(抽象訪问者):为每种详细的被訪问者(ConcreteElement)声明一个訪问操作;

2>、ConcreteVisitor(详细訪问者):实现对被訪问者(ConcreteElement)的详细訪问操作;

3>、Element(抽象被訪问者):通常有一个Accept方法。用来接收/引用一个抽象訪问者对象;

4>、ConcreteElement(详细被訪问者对象):实现Accept抽象方法。通过传入的详细訪问者參数、调用详细訪问者对该对象的訪问操作方法实现訪问逻辑;

5>、Clent、ObjectStructure(client訪问过程測试环境):该过程中,被訪问者通常为一个集合对象,通过对集合的遍历完毕訪问者对每个被訪问元素的訪问操作;


五、用例学习

1.抽象被訪问者:公司员工抽象类  Employee.java

/**
 * 公司员工(被訪问者)抽象类
 * @author  lvzb.software@qq.com
 *
 */
public abstract class Employee {
	
	/**
	 * 接收/引用一个抽象訪问者对象
	 * @param department 抽象訪问者 这里指的是公司部门如 人力资源部、財务部
	 */
	public abstract void accept(Department department);

}
2.详细被訪问者:公司管理岗位员工类 ManagerEmployee.java

/**
 * 公司员工:管理者(详细的被訪问者对象)
 * @author  lvzb.software@qq.com
 * 
 */
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;
	}
	
}
3.详细被訪问者:公司普通岗位员工类 GeneralEmployee.java

/**
 * 公司普通员工(详细的被訪问者对象)
 * @author  lvzb.software@qq.com
 *
 */
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;
	}

}
4.抽象訪问者:公司部门抽象类 Department.java

/**
 * 公司部门(訪问者)抽象类
 * @author  lvzb.software@qq.com
 *
 */
public abstract class Department {
	
	// 声明一组重载的訪问方法,用于訪问不同类型的详细元素(这里指的是不同的员工)  
	
	/**
	 * 抽象方法 訪问公司管理者对象<br/>
	 * 详细訪问对象的什么  就由详细的訪问者子类(这里指的是不同的详细部门)去实现
	 * @param me
	 */
	public abstract void visit(ManagerEmployee me);
	
	/**
	 * 抽象方法 訪问公司普通员工对象<br/>
	 * 详细訪问对象的什么  就由详细的訪问者子类(这里指的是不同的详细部门)去实现
	 * @param ge
	 */
	public abstract void visit(GeneralEmployee ge);

}
5.详细訪问者:公司財务部类 FADepartment.java

/**
 * 详细訪问者对象:公司財务部<br/>
 * 財务部的职责就是负责统计核算员工的工资
 * @author  lvzb.software@qq.com
 *
 */
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);
	}

}
6.详细訪问者:公司人力资源部类 HRDepartment.java

/**
 * 详细訪问者对象:公司人力资源部<br/>
 * 人力资源部的职责就是负责统计核算员工的每月上班时长
 * @author  lvzb.software@qq.com
 *
 */
public class HRDepartment extends Department {

	/**
	 * 訪问公司管理者对象的每月实际上班时长统计
	 */
	@Override
	public void visit(ManagerEmployee me) {
		me.getTotalTimeSheet();
	}

	/**
	 * 訪问公司普通员工对象的每月实际上班时长统计
	 */
	@Override
	public void visit(GeneralEmployee ge) {
		ge.getTotalTimeSheet();
	}

}
7.client測试类:模拟財务部对公司员工的工资核算和訪问 Client.java

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);
		}	
	}
	
}

假设要更改为人力资源部对员工的一个月的上班时长统计 则仅仅要将上述代码中的

FADepartment department = new FADepartment();
改动为例如以下就可以
HRDepartment department = new HRDepartment();

8.程序执行结果:

管理者: 王总  固定工资 =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

六、其它

posted @ 2017-04-24 12:25  cxchanpin  阅读(221)  评论(0编辑  收藏  举报