操作複雜物件結構——訪問者模式(二)

Liuwei-Sunny發表於2012-04-06

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()
	{
		//業務方法
	}
}


      在具體元素類ConcreteElementAaccept()方法中,通過呼叫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

相關文章