天天看點

通路者模式

1.通路者模式是什麼

1.百度百科

通路者模式(Visitor Pattern)表示一個作用于某對象結構中的各元素的操作。它使你可以在不改變各元素類的前提下定義作用于這些元素的新操作。

2.維基百科

In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existent object structures without modifying the structures. It is one way to follow the open/closed principle.

3.lz了解

在父類中添加一個抽象方法 那麼所欲的子類會産生改變。那麼在不更改該父類和子類的結構情況下添加一個作用于子類元素的方法。

在被通路者中建立一個方法的引用。引用調用的是通路者的通路方法。當被通路者調用該方法時,被通路者對象會将本身傳遞給通路者通路。進而達到通路對象的目的。這種通路方式是動态的。

關鍵點:

1.對象結構不改變,比如繼承結構、組合結構等。

2.需要給對象成員變量添加一個新的操作成員變量的方法。

3.用于統一操作所有元素。類似父類操作成員變量的抽象方法。

4.核心角色

抽象通路者角色(Visitor): 它定義了對每一個元素(Element)通路的行為,它的參數就是可以通路的元素,它的方法個數理論上來講與元素個數(Element的實作類個數)是一樣的,從這點不難看出,通路者模式要求元素類的個數不能改變(不能改變的意思是說,如果元素類的個數經常改變,則說明不适合使用通路者模式)。也就是說該接口的實作類的數量需要固定。

具體通路者角色(ConcreteVisitor):它需要給出對每一個元素類通路時所産生的具體行為。也就是實作vistitor接口.

抽象元素角色、抽象被通路角色(Element):元素接口,它定義了一個接受通路者(accept)的方法,其意義是指,每一個元素都要可以被通路者通路。也就是需要将通路者的的通路方式“挂入”被通路角色的對象中。

具體元素角色、具體被通路角色(ConcreteElement):具體的元素類,它提供接受通路方法的具體實作,而這個具體的實作,通常情況下是使用通路者提供的通路該元素類的方法。也就是将被通路者自己和通路者“挂上”。

結構對象角色(ObjectStructure):這個便是定義當中所提到的對象結構,對象結構是一個抽象表述,具體點可以了解為一個具有容器性質或者複合對象特性的類,它會含有一組元素(Element),并且可以疊代這些元素,供通路者通路。

也就是将一系列被通路者放入容器中,友善統一管理。

2.通路者模式解決了什麼問題

項目的抽象結構不改變的情況下是需要對其實作對象中的元素增加一個新的方法。

通路者模式的優點

符合單一職責原則:具體元素角色也就是Employee抽象類的兩個子類負責資料加載,而Visitor類負責報表的呈現,兩個不同的職責非常明确的分離開來,各自演繹變化;

優秀的擴充性:由于職責分開,繼續增加對資料的操作是非常快捷的,例如,想要生成一份不同格式的報表,直接在Visitor中增加一個方法;

靈活性非常高:當資料彙總時,如果要統計所有員工的工資,而且不同類型的員工工資系數不同,則可以通過通路者模式實作。

3.通路者模式用法

以公司員工舉例子,員工有名稱績效年齡功能工資等屬性。每個員工各個屬性不一樣。而公司各個部門關注的員工屬性也不一樣但是都是基于者三個屬性的組合

員工抽象類。抽象元素角色、抽象被通路角色。該角色是被通路的。有一個通路者的鈎子。調用通路者的方法将該對象傳遞給通路者通路。

public interface Employee {
	//通路
	void accept(DepartmentManager departmentManager);
	//擷取姓名
	String getName();
	//擷取年齡
	int getAge();
	//擷取工資
	double getSalary();
	//擷取績效
	int getPerformance();

}

           

具體的員工、具體元素角色、具體被通路角色

為了簡便這裡隻有一種最普通的員工。

public class Emp implements Employee {

	private String name;
	private int age;
	private double salary;
	private int performance;

	public Emp(String name, int age, double salary, int performance) {
		super();
		this.name = name;
		this.age = age;
		this.salary = salary;
		this.performance = performance;
	}

	@Override
	public String toString() {
		return "Emp [name=" + name + ", age=" + age + ", salary=" + salary + ", performance=" + performance + "]";
	}


	public void setName(String name) {
		this.name = name;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	public void setPerformance(int performance) {
		this.performance = performance;
	}

	@Override
	public void accept(DepartmentManager departmentManager) {
		departmentManager.view(this);
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public int getAge() {
		return age;
	}

	@Override
	public double getSalary() {
		return salary;
	}

	@Override
	public int getPerformance() {
		return performance;
	}

}

           

部門經理、抽象通路者角色。定義被通路者回調那個方法将自己傳送給通路者通路。

public interface DepartmentManager {

	public void view(Emp emp);

}
           

HR、PM 具體通路者角色 含有部分拓展業務。

//hr部門 統計平均工資 統計平均年齡 統計員工數 員工工資
public class HR implements DepartmentManager {

	private double totalAge;

	private double totalSalary;

	private double num = 0.0;

	@Override
	public void view(Emp emp) {
		totalAge += emp.getAge();
		totalSalary += emp.getSalary();
		num++;

	}

	public double avgAge() {
		return totalAge / num;
	}

	public double avgSalary() {
		return totalSalary / num;
	}

	public double getTotalAge() {
		return totalAge;
	}

	public double getTotalSalary() {
		return totalSalary;
	}

	public double getNum() {
		return num;
	}

}

public class PM implements DepartmentManager {

	private double totalPerformance;

	private double totalSalary;

	private double num = 0;

	@Override
	public void view(Emp emp) {
		totalPerformance += emp.getPerformance();
		totalSalary += emp.getSalary();
		num++;
	}

	public double avgPerformance() {
		return totalPerformance/num;
	}

	public double PerformanceForSalary() {
		return totalSalary/totalPerformance;
	}


	public double getTotalPerformance() {
		return totalPerformance;
	}

	public double getTotalSalary() {
		return totalSalary;
	}

	public double getNum() {
		return num;
	}

}
           

員工清單角色、結構對象角色

用于批量處理大量被通路角色的聚合任務、這個角色并不是必須的。

public class EmpList {

	private List<Employee> empList = new ArrayList<Employee>();

	public void addEmp(Employee emp) {
		empList.add(emp);
	}

	public void show (DepartmentManager departmentManager) {
		for(Employee emp:empList) {
			emp.accept(departmentManager);
		}
	}
}


           

這裡我們用測試下該類的執行效果

public class Report {

	public static void main(String[] args) {
		//員工大會開始
		EmpList empList = new EmpList();
		//員工開始入場
		Emp emp1 = new Emp("罡大爺",36,18000.0,86);
		empList.addEmp(emp1);
		Emp emp2 = new Emp("張大爺",29,28000.0,90);
		empList.addEmp(emp2);
		Emp emp3 = new Emp("封大爺",30,25000.0,82);
		empList.addEmp(emp3);
		Emp emp4 = new Emp("任大爺",24,15000.0,79);
		empList.addEmp(emp4);
		Emp emp5 = new Emp("王二爺",22,12000.0,70);
		empList.addEmp(emp5);
		//員工入場完畢
		//下面有請人力資源部經理作報告
		HR hr =  new HR();
		empList.show(hr);
		System.out.println("目前我司員工總計:"+hr.getNum()+"人,平均年齡:"+hr.avgAge()+" 平均工資:"+hr.avgSalary()+" 實發工資總計:"+hr.getTotalSalary());
		//下面有請項目經理做報告
		PM pm = new PM();
		empList.show(pm);
		System.out.println("我司全體員工平均績效為:"+pm.avgPerformance()+" 每點績效付出的工資為:"+pm.PerformanceForSalary());
		//員工大會結束
	}
}
           

結果

目前我司員工總計:5.0人,平均年齡:28.2 平均工資:19600.0 實發工資總計:98000.0
我司全體員工平均績效為:81.4 每點績效付出的工資為:240.7862407862408

這裡對數字的類型選擇并沒有考究希望讀者見諒。

4.通路者模式的問題

被通路者細節公開 通路者要通路一個類就必然要求這個類公布一些方法和資料,也就是通路者關注了其他類的内部細節,這是迪米特法則所不建議的;

具體被通路者數量變動困難 具體元素角色的增加、删除、修改都是比較困難的,如上例中增加一個年齡字段,則Visitor就需要修改,如果有多個Visitor那麼會改動很大;

違背了依賴倒置的原則 通路者依賴的是具體元素,而不是抽象元素,這違背了依賴倒置原則,特别是在面向對象的程式設計中,抛棄對接口的依賴,而直接依賴實作類,擴充比較困難。上例中的抽象部門經理角色依賴的就是具體的被通路者角色。相當于抽象依賴具體,這是違反依賴倒置原則的。

5.通路者模式總結

使用場景:

一個對象結構包含很多類對象,他們有不同的接口,想對這些對象實施一些依賴于其具體類的操作,也就是用疊代器模式已經不能勝任的場景。

需要對一個對象結構中的對象進行很多不同并且不相關的操作,而你想避免讓這些操作“污染”這些對象的類。

碎碎念:

通路者模式确實是比較難以了解的設計模式。其難點在于通路者和被通路者互相挂鈎這塊的邏輯。當調用被通路者的通路方法時,被通路者會調用通路者的方法并将自己的執行個體作為參數傳入通路者的方法中,讓通路者調用被通路的傳來的執行個體以便于完成其他操作或者業務的添加。動态的添加更多的通路者可以在完全不破壞被通路者的前提下增加被通路者的業務邏輯。其拓展性也是比較優秀的。但是好處必然伴随着風險,就是當被通路者添加新的成員變量時,當舊通路者想讀取新成員變量就會産生巨大的災難,是以說這種拓展隻适合有固定接口固定實作數量的被通路對象的方法拓展。并不适合實作數量不固定或者其成員變量不固定的對象進行方法拓展。從這個模式中我真的感覺到了自己基礎知識的不紮實。對JDK動态選擇方法的原理一無所知。我建議同學們要學習通路者模式必須了解下動态分派和靜态分派。這樣了解通路者模式會輕松很多。

引用

http://www.cnblogs.com/zuoxiaolong/p/pattern23.html