天天看點

操作複雜對象結構——通路者模式(二)

26.2 通路者模式概述

      通路者模式是一種較為複雜的行為型設計模式,它包含通路者和被通路元素兩個主要組成部分,這些被通路的元素通常具有不同的類型,且不同的通路者可以對它們進行不同的通路操作。例如處方單中的各種藥品資訊就是被通路的元素,而劃價人員和藥房從業人員就是通路者。通路者模式使得使用者可以在不修改現有系統的情況下擴充系統的功能,為這些不同類型的元素增加新的操作。

      在使用通路者模式時,被通路元素通常不是單獨存在的,它們存儲在一個集合中,這個集合被稱為“對象結構”,通路者通過周遊對象結構實作對其中存儲的元素的逐個操作。

      通路者模式定義如下:

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

      通路者模式的結構較為複雜,其結構如圖26-2所示:

操作複雜對象結構——通路者模式(二)

      在通路者模式結構圖中包含如下幾個角色:

      ●Vistor(抽象通路者):抽象通路者為對象結構中每一個具體元素類ConcreteElement聲明一個通路操作,從這個操作的名稱或參數類型可以清楚知道需要通路的具體元素的類型,具體通路者需要實作這些操作方法,定義對這些元素的通路操作。

      ●ConcreteVisitor(具體通路者):具體通路者實作了每個由抽象通路者聲明的操作,每一個操作用于通路對象結構中一種類型的元素。

      ●Element(抽象元素):抽象元素一般是抽象類或者接口,它定義一個accept()方法,該方法通常以一個抽象通路者作為參數。【稍後将介紹為什麼要這樣設計。】

      ●ConcreteElement(具體元素):具體元素實作了accept()方法,在accept()方法中調用通路者的通路方法以便完成對一個元素的操作。

      ● ObjectStructure(對象結構):對象結構是一個元素的集合,它用于存放元素對象,并且提供了周遊其内部元素的方法。它可以結合組合模式來實作,也可以是一個簡單的集合對象,如一個List對象或一個Set對象。

      通路者模式中對象結構存儲了不同類型的元素對象,以供不同通路者通路。通路者模式包括兩個層次結構,一個是通路者層次結構,提供了抽象通路者和具體通路者,一個是元素層次結構,提供了抽象元素和具體元素。相同的通路者可以以不同的方式通路不同的元素,相同的元素可以接受不同通路者以不同通路方式通路。在通路者模式中,增加新的通路者無須修改原有系統,系統具有較好的可擴充性。

      在通路者模式中,抽象通路者定義了通路元素對象的方法,通常為每一種類型的元素對象都提供一個通路方法,而具體通路者可以實作這些通路方法。這些通路方法的命名一般有兩種方式:一種是直接在方法名中标明待通路元素對象的具體類型,如visitElementA(ElementA elementA),還有一種是統一取名為visit(),通過參數類型的不同來定義一系列重載的visit()方法。當然,如果所有的通路者對某一類型的元素的通路操作都相同,則可以将操作代碼移到抽象通路者類中,其典型代碼如下所示:

abstract class Visitor
{
	public abstract void visit(ConcreteElementA elementA);
	public abstract void visit(ConcreteElementB elementB);
	public void visit(ConcreteElementC elementC)
	{
		//元素ConcreteElementC操作代碼
	}
}
           

      在這裡使用了重載visit()方法的方式來定義多個方法用于操作不同類型的元素對象。在抽象通路者Visitor類的子類ConcreteVisitor中實作了抽象的通路方法,用于定義對不同類型元素對象的操作,具體通路者類典型代碼如下所示:

class ConcreteVisitor extends Visitor
{
	public void visit(ConcreteElementA elementA)
	{
		//元素ConcreteElementA操作代碼
	}
	public void visit(ConcreteElementB elementB)
	{
		//元素ConcreteElementB操作代碼
	}
}
           

      對于元素類而言,在其中一般都定義了一個accept()方法,用于接受通路者的通路,典型的抽象元素類代碼如下所示:

interface Element
{
	public void accept(Visitor visitor);
}
           

      需要注意的是該方法傳入了一個抽象通路者Visitor類型的參數,即針對抽象通路者進行程式設計,而不是具體通路者,在程式運作時再确定具體通路者的類型,并調用具體通路者對象的visit()方法實作對元素對象的操作。在抽象元素類Element的子類中實作了accept()方法,用于接受通路者的通路,在具體元素類中還可以定義不同類型的元素所特有的業務方法,其典型代碼如下所示:

class ConcreteElementA implements Element
{
	public void accept(Visitor visitor)
	{
		visitor.visit(this);
	}
	
	public void operationA()
	{
		//業務方法
	}
}
           

      在具體元素類ConcreteElementA的accept()方法中,通過調用Visitor類的visit()方法實作對元素的通路,并以目前對象作為visit()方法的參數。其具體執行過程如下:

      (1) 調用具體元素類的accept(Visitor visitor)方法,并将Visitor子類對象作為其參數;

      (2) 在具體元素類accept(Visitor visitor)方法内部調用傳入的Visitor對象的visit()方法,如visit(ConcreteElementA elementA),将目前具體元素類對象(this)作為參數,如visitor.visit(this);

      (3) 執行Visitor對象的visit()方法,在其中還可以調用具體元素對象的業務方法。

      這種調用機制也稱為“雙重分派”,正因為使用了雙重分派機制,使得增加新的通路者無須修改現有類庫代碼,隻需将新的通路者對象作為參數傳入具體元素對象的accept()方法,程式運作時将回調在新增Visitor類中定義的visit()方法,進而增加新的元素通路方式。

思考

雙重分派機制如何用代碼實作?

      在通路者模式中,對象結構是一個集合,它用于存儲元素對象并接受通路者的通路,其典型代碼如下所示:

class ObjectStructure
{
	private ArrayList<Element> list = new ArrayList<Element>(); //定義一個集合用于存儲元素對象

	public void accept(Visitor visitor)
	{
		Iterator i=list.iterator();
		
		while(i.hasNext())
		{
			((Element)i.next()).accept(visitor); //周遊通路集合中的每一個元素
		}
	}

	public void addElement(Element element)
	{
		list.add(element);
	}

	public void removeElement(Element element)
	{
		list.remove(element);
	}
}
           

      在對象結構中可以使用疊代器對存儲在集合中的元素對象進行周遊,并逐個調用每一個對象的accept()方法,實作對元素對象的通路操作。

思考

通路者模式是否符合“開閉原則”?【從增加新的通路者和增加新的元素兩方面考慮。】

【作者:劉偉 http://blog.csdn.net/lovelion】