天天看點

19.通路者模式(Visitor Pattern)

話說有一個銀行,有三個視窗,但是每個視窗的智能都是一樣的,即都能辦理所有的業務。是以每位來銀行辦理業務的人隻要排隊就是了,排到你了,就向業務員說明你要辦理的業務,然後業務員根據你的業務選擇不同的單據,打開不同的賬本……

業務員此時典型的工作流程是:

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等,在項目中,一般很少抽象出這個角色。

它的通用類圖是:

19.通路者模式(Visitor Pattern)

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展現等,我們還可以與其他模式混編建立一套自己的過濾器或者攔截器。

繼續閱讀