話說有一個銀行,有三個視窗,但是每個視窗的智能都是一樣的,即都能辦理所有的業務。是以每位來銀行辦理業務的人隻要排隊就是了,排到你了,就向業務員說明你要辦理的業務,然後業務員根據你的業務選擇不同的單據,打開不同的賬本……
業務員此時典型的工作流程是:
if (service instanceof Saving){
//存款
......
}else if (service instanceof Draw){
//提款
......
}else if (service instanceof Fund){
//基金
......
}
......
于是每位業務員的桌面總是塞得滿滿的,更重要的是大量的時間都花在受理不同業務之間的切換,使得效率很低。
有沒有方法能夠使得業務員的工作效率提高呢?銀行經理苦思冥想了半天,終于想出了一個好辦法。他讓每個視窗各負責一個業務,同時委任了一位通路者 (Visitor),負責在客戶進門時,詢問他要辦理什麼業務,告訴他應該去哪個視窗辦理。這樣,每個視窗的業務員就隻負責一項業務,減少了在不同業務間切換的時間耗費 ,效率大大提高。更重要的是,當某一項業務的處理流程發生變更時,不需要同時麻煩三個視窗的業務員,而隻需要讓處理這項業務的業務員進行修改就可以了 。
1.定義
封裝一些作用于某種資料結構中的各元素的操作,它可以在不改變資料結構的前提下定義作用于這些元素的新的操作。
通路者的實作依賴了Java的重載機制。
2.通路者模式的五個角色
- Visitor-抽象通路者:抽象類或者接口,聲明通路者可以通路哪些元素,具體到程式中就是visit方法的參數定義哪些對象是可以被通路的。
- ConcreteVisitor-具體通路者:它影響通路者通路到一個類後該怎麼幹,要做什麼事情(定義中所謂的操作)。
- Element-抽象元素:接口或者抽象類,聲明接受哪一類的通路者通路,程式上是通過accept方法中的參數來定義的。
- ConcreteElement-具體元素:實作accept方法,通常是visitor.visit(this),基本都形成一種模式了。
- ObjectStructure-結構對象:元素産生者,一般容納在多個不同類、不同接口的容器,如List、Set、Map等,在項目中,一般很少抽象出這個角色。
它的通用類圖是:
3.通路者模式的使用場景
- 一個對象結構(集合)包含很多類對象,它們有不同的接口,而你想對這些對象實施一些依賴于其具體類的操作,也就是說疊代器模式已經不能勝任的時候。
- 需要對一個對象結構中的對象進行很多不同并且不想管的操作,而你想避免讓這些操作污染這些對象的類。
總結一下,在這種地方你一定要考慮使用通路者模式:業務規則要求周遊多個不同的對象。這本身也是通路者模式的出發點,疊代器模式隻能通路同類或同接口的資料(當然你可以使用instanceof來判斷,不過不建議這麼做),而通路者模式是對疊代器模式的擴充,可以周遊不同的對象,然後執行不同的操作,也就是針對通路的對象不同,執行不同的操作。
4.通路者模式的通用代碼
package _19VisitorPattern;
/**
* 抽象通路者
*/
public interface IVisitor {
// 可以通路哪些對象
public void visit(ConcreteElement1 el1);
public void visit(ConcreteElement2 el2);
}
package _19VisitorPattern;
/**
* 具體通路者
*/
public class Visitor implements IVisitor {
// 通路el1元素
@Override
public void visit(ConcreteElement1 el1) {
el1.doSomething();
}
// 通路el2元素
@Override
public void visit(ConcreteElement2 el2) {
el2.doSomething();
}
}
package _19VisitorPattern;
/**
* 抽象元素類
*/
public abstract class Element {
// 定義業務邏輯
public abstract void doSomething();
// 允許誰來通路
public abstract void accept(IVisitor iVisitor);
}
package _19VisitorPattern;
/**
* 具體元素類1
*/
public class ConcreteElement1 extends Element {
// 完善業務邏輯
@Override
public void doSomething() {
// 處理業務邏輯
}
// 允許哪個通路者通路
@Override
public void accept(IVisitor iVisitor) {
iVisitor.visit(this);
}
}
package _19VisitorPattern;
/**
* 具體元素類2
*/
public class ConcreteElement2 extends Element {
// 完善業務邏輯
@Override
public void doSomething() {
// 處理業務邏輯
}
// 允許哪個通路者通路
@Override
public void accept(IVisitor iVisitor) {
iVisitor.visit(this);
}
}
package _19VisitorPattern;
import java.util.Random;
/**
* 結構對象
*/
public class ObjectStructure {
public static Element creatElement()
{
Random random = new Random();
if(random.nextInt(100) > 50)
{
return new ConcreteElement1();
}else{
return new ConcreteElement2();
}
}
}
package _19VisitorPattern;
/**
* 場景類
*/
public class Client {
public static void main(String[] args) {
for(int i=0;i<10;i++)
{
Element element = ObjectStructure.creatElement();
// 接受通路者通路
element.accept(new Visitor());
}
}
}
5.通路者模式的優點
- 符合單一職責原則:具體元素角色負責資料的加載,而Visitor類則負責資料的展現,兩個不同的職責非常明确地分離開來。
- 優秀的擴充性:直接在Visitor中新增方法或者,直接新增Visitor通路者。
- 靈活性非常高:由于職責分明,耦合性非常低,是以可以非常靈活的組合不同的資料和通路者。
6.通路者模式的缺點:
- 具體元素對通路者公布細節:通路者需要通路一個類,就必須要求這個類公布一些方法和資料,也就是說通路者關注了其他類的内部細節,這是迪米特法則不建議的。
- 具體元素變更困難:具體元素增加或删除任意成員變量都将影響到通路者的實作細節。
- 違背了依賴倒置原則:通路者依賴的是具體的元素,而不是抽象元素,這破壞了依賴倒置原則,擴充比較困難。
7.通路者模式的擴充
統計功能
多個通路者
實作雙分派:Java可以通過通路者模式實作支援雙分派(其實Java是一門單分派語言)
8.最佳實踐
通路者模式是一種集中規整模式,特别适用于大規模重構的項目,在這一個階段需求已經非常清晰,原系統的功能點也已經明确,通過通路者模式可以非常容易把一些功能進行梳理,達到最終目的-功能集中化,如一個統一的報表運算、UI展現等,我們還可以與其他模式混編建立一套自己的過濾器或者攔截器。