閱讀628 返回首頁    go 技術社區[雲棲]


Visitor Pattern 和 double-dispatch

Override VS. Overload

    Simple Polymorphism (Override) the object whose method is called is decided run-time
    multi- polymorphism (Overload)the object which method is called  is decided upon the type of the argument.

upcast is implicit, 向上轉型是隱式的, downcast是顯示的。

也就是說當你說正方形是形狀的時候,是可以的。但是當你說形狀是正方形的時候,你必須顯示的用括號轉型(square)shapeA

如果不幸,此時的形狀是圓形的話,it will be crash at runtime, and give you java.lang.ClassCastException 


看這樣一個例子:

public class ColoredHorses {
	public void display(Horse h) {	System.out.println("Horse:" + h.message());	}//哪個message方法被調用,這個要看運行時的對象了
	public void display(WhiteHorse wh) {	System.out.println("WhiteHorse:" + wh.message());	}
	public void display(BlackHorse bh) {	System.out.println("BlackHorse:" + bh.message());	}

	public static void main(String[] args) {
		ColoredHorses test = new ColoredHorses();
		Horse h1 = new WhiteHorse(); //h1 被申明為Horse,但是指向WhiteHorse對象
		Horse h2 = new BlackHorse();
		WhiteHorse wh = new WhiteHorse();
		BlackHorse bh = new BlackHorse();

		test.display(h1);//哪個display方法被調用呢?因為type是Horse,所以是display(Horse h)
		test.display(h2);//如果沒有display(Horse),將會報編譯錯誤,除非你顯示轉型到一個子類如(BlackHorse)h2
		test.display(wh);//即使沒有display(WhiteHorse)也不會有編譯錯,因為可以隱式upcast到Horse,然後調用display(Horse)
		test.display(bh);
	}
}

class Horse				 {	protected String message()	 { return "I am a horse";	};	};

class WhiteHorse extends Horse {	protected String message() 	{ return "I am a white horse";	};	};

class BlackHorse extends Horse {	protected String message() 	{ return "I am a black horse";	};	};

其輸出是什麼呢?

Horse:I am a white horse
Horse:I am a black horse
WhiteHorse:I am a white horse
BlackHorse:I am a black horse

Visitor Pattern

訪問者模式,顧名思義使用了這個模式後就可以在不修改已有程序結構的前提下,通過添加額外的“訪問者”來完成對已有代碼功能的提升。

  《設計模式》一書對於訪問者模式給出的定義為:表示一個作用於某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。從定義可以看出結構對象是使用訪問者模式必須條件,而且這個結構對象必須存在遍曆自身各個對象的方法。這便類似於java中的collection概念了。

  以下是訪問者模式的組成結構:

  1) 訪問者角色(Visitor):為該對象結構中具體元素角色聲明一個訪問操作接口。該操作接口的名字和參數標識了發送訪問請求給具體訪問者的具體元素角色。這樣訪問者就可以通過該元素角色的特定接口直接訪問它。

  2) 具體訪問者角色(Concrete Visitor):實現每個由訪問者角色(Visitor)聲明的操作。

  3) 元素角色(Element):定義一個Accept操作,它以一個訪問者為參數。

  4) 具體元素角色(Concrete Element):實現由元素角色提供的Accept操作。

  5) 對象結構角色(Object Structure):這是使用訪問者模式必備的角色。它要具備以下特征:能枚舉它的元素;可以提供一個高層的接口以允許該訪問者訪問它的元素;可以是一個複合(組合模式)或是一個集合,如一個列表或一個無序集合。

廢話少說,直接上例子:

import java.util.ArrayList;
import java.util.List;

public class VistorPattern {

	public static void main(String[] args){
		FlowersCompo flowers = new FlowersCompo();
		flowers.addFlower(new Gladiolus());
		flowers.addFlower(new Runuculus());
		flowers.accept(new BeeVisitor());
	}
}

// 訪問者角色

interface Visitor {
	void visit(Gladiolus g);

	void visit(Runuculus r);

	void visit(Chrysanthemum c);
}

// The Flower hierarchy cannot be changed:
// 元素角色

interface Flower {
	void accept(Visitor v);
}

// 以下三個具體元素角色

class Gladiolus implements Flower {
	public void accept(Visitor v) {
		v.visit(this);
	}
}

class Runuculus implements Flower {
	public void accept(Visitor v) {
		v.visit(this);
	}
}

class Chrysanthemum implements Flower {
	public void accept(Visitor v) {
		v.visit(this);
	}
}

// 對象結構角色(Object Structure)
class FlowersCompo{
	List<Flower> flowers = new ArrayList<Flower>();
	
	public void addFlower(Flower f){
		flowers.add(f);
	}
	
	public void removeFlower(Flower f){
		flowers.remove(f);
	}
	
	public void accept(Visitor v){
		for (Flower f : flowers){
			f.accept(v);
		}
	}
}

// Add the ability to produce a string:
// 實現的具體訪問者角色
class StrVisitor implements Visitor {
	String s;

	public String toString() {
		return s;
	}

	public void visit(Gladiolus g) {
		s = "Gladiolus";
	}

	public void visit(Runuculus r) {
		s = "Runuculus";
	}

	public void visit(Chrysanthemum c) {
		s = "Chrysanthemum";
	}
}

// Add the ability to do "Bee" activities:
// 另一個具體訪問者角色

class BeeVisitor implements Visitor {
	public void visit(Gladiolus g) {
		System.out.println("Bee and Gladiolus");
	}

	public void visit(Runuculus r) {
		System.out.println("Bee and Runuculus");
	}

	public void visit(Chrysanthemum c) {
		System.out.println("Bee and Chrysanthemum");
	}
}

一種典型的使用Visitor的場景是,有一組數據,需要有不同的展現形式,這樣當對象結構發生改變時,我們隻需要改變Object Structure對象,增加或減少裏麵的Element。

當需要一個新的展現形式時,隻需要實現一個新的Visitor。


因為容易發生變化的部分(對象結構,展現形式)得到了封裝和解耦,並且可以獨立的擴展,整個設計具有比較好的彈性。


不用accept(),用Visitor直接去visit() 元素對象,可以嗎?

當然是可以的,而且這樣還省去了Element對Visitor的依賴,進一步降低了耦合,我們把上麵的程序改成不用accept 的試試

將Object Structure 修改如下:

class FlowersCompo{
	List<Flower> flowers = new ArrayList<Flower>();
	
	public void addFlower(Flower f){
		flowers.add(f);
	}
	
	public void removeFlower(Flower f){
		flowers.remove(f);
	}
	
	public void accept(Visitor v){
		for (Flower f : flowers){
			// f.accept(v);
			v.visit(f); // 用Visitor直接去訪問Element, 編譯器報錯了
		}
	}
}
改完程序後,發現編譯器報錯了,提示沒有定義visit(Flower)的方法,根據上麵關於override, overload 的闡述,在overload 中which method is called 是在編譯時靜態綁定的,查看一下,我們雖然定義了 visit(Gladiolus g),visit(Runuculus r) ,visit(Chrysanthemum c) 但是沒有定義visit(Flower)方法

那就在Visitor中定義上唄:

interface Visitor {
	void visit(Flower f);//增加對Flower接口visit的功能
	
	void visit(Gladiolus g);

	void visit(Runuculus r);

	void visit(Chrysanthemum c);
}

接口發生了改變,因此每個ConcreteVisitor都要實現這個visit(Flower),為了在Runtime時區分真正的對象是什麼,BeeVisitor大概要這樣寫:

class BeeVisitor implements Visitor {
	
	public void visit (Flower f){	// 通過instanceof來判斷運行時對象,並調用被overload的具體方法	
		if (f instanceof Gladiolus){
			visit((Gladiolus)f);
		}
		else if (f instanceof Runuculus){
			visit((Runuculus)f);
		}
		else if (f instanceof Chrysanthemum){
			visit((Chrysanthemum)f);
		}
	}
	
	public void visit(Gladiolus g) {
		System.out.println("Bee and Gladiolus");
	}

	public void visit(Runuculus r) {
		System.out.println("Bee and Runuculus");
	}

	public void visit(Chrysanthemum c) {
		System.out.println("Bee and Chrysanthemum");
	}
}

如果這樣修改,每一個Visitor實現都要重寫同樣的visit(Flower)代碼,做個改動,把visit(Flower)放到一個抽象方法中去,下麵是去掉accept後的Visitor

import java.util.ArrayList;
import java.util.List;

public class VistorPattern {

	public static void main(String[] args){
		FlowersCompo flowers = new FlowersCompo();
		flowers.addFlower(new Gladiolus());
		flowers.addFlower(new Runuculus());
		flowers.accept(new BeeVisitor());
	}
}

// 訪問者角色

interface Visitor {
	public void visit(Flower f);
	
	public void visit(Gladiolus g);

	public void visit(Runuculus r);

	public void visit(Chrysanthemum c);
}

abstract class AbstractVisitor implements Visitor {
	public void visit (Flower f){		
		if (f instanceof Gladiolus){
			visit((Gladiolus)f);
		}
		else if (f instanceof Runuculus){
			visit((Runuculus)f);
		}
		else if (f instanceof Chrysanthemum){
			visit((Chrysanthemum)f);
		}
	}
}

// The Flower hierarchy cannot be changed:
// 元素角色

interface Flower {
}

// 以下三個具體元素角色

class Gladiolus implements Flower {}

class Runuculus implements Flower {}

class Chrysanthemum implements Flower {}


// 對象結構角色(Object Structure)
class FlowersCompo{
	List<Flower> flowers = new ArrayList<Flower>();
	
	public void addFlower(Flower f){
		flowers.add(f);
	}
	
	public void removeFlower(Flower f){
		flowers.remove(f);
	}
	
	public void accept(Visitor v){
		for (Flower f : flowers){
			// f.accept(v);
			v.visit(f);
		}
	}
}


// Add the ability to produce a string:
// 實現的具體訪問者角色
class StrVisitor extends AbstractVisitor {
	String s;

	public String toString() {
		return s;
	}

	public void visit(Gladiolus g) {
		s = "Gladiolus";
	}

	public void visit(Runuculus r) {
		s = "Runuculus";
	}

	public void visit(Chrysanthemum c) {
		s = "Chrysanthemum";
	}
}

// Add the ability to do "Bee" activities:
// 另一個具體訪問者角色

class BeeVisitor extends AbstractVisitor {
	
	public void visit(Gladiolus g) {
		System.out.println("Bee and Gladiolus");
	}

	public void visit(Runuculus r) {
		System.out.println("Bee and Runuculus");
	}

	public void visit(Chrysanthemum c) {
		System.out.println("Bee and Chrysanthemum");
	}
}

再回頭看看,在正統的Visitor模式中為什麼要用accept(),其實這是一個double-dispatch 模式,仔細觀察下concreteElement的accept實現

class Chrysanthemum implements Flower {
	public void accept(Visitor v) {
		v.visit(this);//用visit(this)解決了visit(Flower)問題
	}
}

既然你不能在runtime決定調用哪個visit()方法,那麼我把你請進來,然後再讓你調用我,其實就是用一個技巧解決了我們必須要用instanceof 做區分的問題。

在我看來,使不使用accept()都是Visitor模式,關鍵是要理解其要義和應該使用的場景。

其核心都是麵向對象的一些基本設計原則:封裝變化原則,Open-Close原則 等等


注意:

在使用Visitor時,被visit的類結構應該盡量穩定,因為新加一個被visit對象就意味著你要修改Visit的interface及所有concreteVisitor。

或者使用組合模式來組裝需要被visit的對象,也是個不錯的選擇。

最後更新:2017-04-02 15:15:26

  上一篇:go Maven學習一之安裝maven以及IDE配置
  下一篇:go 《軒轅劍之天之痕》開創周播新模式 明星陣容俘獲眾多年輕觀眾