JAVA設計模式之 訪問者模式【Visitor Pattern】

小呂-ICE發表於2014-11-15

一、概述

    訪問者模式是一種較為複雜的行為型設計模式,它包含訪問者和被訪問元素兩個主要組成部分,這些被訪問的元素通常具有不同的型別,且不同的訪問者可以對它們進行不同的訪問操作。在使用訪問者模式時,被訪問元素通常不是單獨存在的,它們儲存在一個集合中,這個集合被稱為“物件結構”,訪問者通過遍歷物件結構實現對其中儲存的元素的逐個操作。訪問者模式是一種物件行為型模式。


二、適用場景

    當有多種型別的訪問者(或是操作者) 對一組被訪問者物件集合(或是物件結構)進行操作(其中物件集合也包含多種型別物件),不同的訪問者型別對每一種具體的被訪問者物件提供不同的訪問操作,每種訪問者型別物件對不同的被訪問者也有不同的訪問操作,那麼這種場景就非常適用訪問者模式。

    如果前面這幾句比較繞的文字說明沒看明白,那麼小呂就舉例一個生活中的業務場景:

    你所在的公司每個月人力資源部要對所有員工進行上班時長、加班時長統計,而財務部要對所有員工進行工資核算,不同職位的員工薪資核算標準肯定不一樣啊,這個大家都明白。在這個案例中人力資源部和財務部是兩個不同型別的部門(訪問者),所有員工(被訪問者)是一個物件集合,而員工又劃分為管理者和技術者兩種型別(備註:這裡小呂只是簡單劃分為兩類),在每月的統計中,人力資源部需要分別對員工進行上班時長和加班時長進行統計,而財務部需要對不同職位的員工進行薪資核算,可見不同部門職責不同,及對員工的訪問操作不同、每個部門對不同型別的員工的訪問操作也不同。那麼針對這種場景  我們有必要了解一下訪問者模式。


三、UML類圖


四、參與者

1>、Visitor(抽象訪問者):為每種具體的被訪問者(ConcreteElement)宣告一個訪問操作;

2>、ConcreteVisitor(具體訪問者):實現對被訪問者(ConcreteElement)的具體訪問操作;

3>、Element(抽象被訪問者):通常有一個Accept方法,用來接收/引用一個抽象訪問者物件;

4>、ConcreteElement(具體被訪問者物件):實現Accept抽象方法,通過傳入的具體訪問者引數、呼叫具體訪問者對該物件的訪問操作方法實現訪問邏輯;

5>、Clent、ObjectStructure(客戶端訪問過程測試環境):該過程中,被訪問者通常為一個集合物件,通過對集合的遍歷完成訪問者對每一個被訪問元素的訪問操作;


五、用例學習

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.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

六、其他

相關文章