1. 程式人生 > >訪問者模式 雙重分派分析 與 accept方法存在的必要性

訪問者模式 雙重分派分析 與 accept方法存在的必要性

以前看過大話設計模式,對各種模式有過一點了解,可是沒怎麼用,可沒怎麼深入去了解所以忘得很快,現在沉下心來好好研究下。但是,在看到訪問者模式的時候,我對accept存在的必要性很是不理解,覺得為何不直接呼叫visitor的visit方法去訪問 Element,不更直接,而且去除雙向依賴,也是一個好的設計。於是百度搜索了N久,看到一篇文章,然後親自測試,終於領會到accept存在的必要性,在此分享出來,供有意或者參考。

以下是我的驗證程式碼:

Element類:

package com.lh.test;

public abstract class Element {

	public abstract void accept(IVisitor visitor);
}

Element A派生:
public class ConcreteElementA extends Element {

	@Override
	public void accept(IVisitor visitor) {
		System.out.println("--ConcreteElementA-- 的方法--accept--呼叫");
		visitor.visit(this);
	}

	public void myfuncA(){
		System.out.println("--ConcreteElementA-- 的方法--myfuncA--呼叫");
	}
}
Element B派生:

public class ConcreteElementB extends Element {



	@Override
	public void accept(IVisitor visitor) {
		System.out.println("--ConcreteElementB-- 的方法--accept--呼叫");
		visitor.visit(this);
	}
	public void myfuncB(){
		System.out.println("--ConcreteElementB-- 的方法--myfuncB--呼叫");
	}
}


IVisitor :注意 visit(Element element) 這個方法,他會讓你知道 accept 雙重分派的必要性,採用模板方法模式

public abstract class IVisitor {
	public void visit(Element element){
		System.out.println("我在處理---Element---物件");
	}
	public abstract void visit(ConcreteElementA elementA);
	public abstract void visit(ConcreteElementB elementB);
}

MyVisitor:
public class MyVisitor extends IVisitor {

	@Override
	public void visit(ConcreteElementA elementA) {
		System.out.println("我在處理---Element-A--物件");
		elementA.myfuncA();
	}

	@Override
	public void visit(ConcreteElementB elementB) {
		System.out.println("我在處理---Element-B--物件");
		elementB.myfuncB();
	}

}

MyVisitorB:對父類的已實現方法進行了重寫
public class MyVisitorB extends IVisitor {

	@Override
	public void visit(ConcreteElementA elementA) {
		System.out.println("--B--在處理---Element-A--物件");
		elementA.myfuncA();
	}

	@Override
	public void visit(ConcreteElementB elementB) {
		System.out.println("--B--在處理---Element-B--物件");
		elementB.myfuncB();
	}

	@Override
	public void visit(Element element) {
		System.out.println("--B--在處理---Element--物件");
	
	}
}

測試流程:

		ConcreteElementA elementA=new ConcreteElementA();
		ConcreteElementB elementB=new ConcreteElementB();
		
		IVisitor visitor=new MyVisitor();
		
		System.out.println("=========靜態分派=========");
		visitor.visit(elementA);
		visitor.visit(elementB);
		System.out.println("=========靜態分派=向上轉型後========");
		Element element=elementA;
		visitor.visit(element);
		element=elementB;
		visitor.visit(element);
		System.out.println("=========動態分派=========");
		elementA.accept(visitor);
		elementB.accept(visitor);
		System.out.println("==================");
		
		System.out.println("======*******=======");

		visitor=new MyVisitorB();
		
		System.out.println("=========靜態分派=========");
		visitor.visit(elementA);
		visitor.visit(elementB);
		System.out.println("=========靜態分派=向上轉型後========");
		 element=elementA;
		visitor.visit(element);
		element=elementB;
		visitor.visit(element);
		System.out.println("=========動態分派=========");
		elementA.accept(visitor);
		elementB.accept(visitor);
		System.out.println("==================");

輸出 :
=========靜態分派=========
我在處理---Element-A--物件
--ConcreteElementA-- 的方法--myfuncA--呼叫
我在處理---Element-B--物件
--ConcreteElementB-- 的方法--myfuncB--呼叫
=========靜態分派=向上轉型後========
我在處理---Element---物件
我在處理---Element---物件
=========動態分派=========
--ConcreteElementA-- 的方法--accept--呼叫
我在處理---Element-A--物件
--ConcreteElementA-- 的方法--myfuncA--呼叫
--ConcreteElementB-- 的方法--accept--呼叫
我在處理---Element-B--物件
--ConcreteElementB-- 的方法--myfuncB--呼叫
==================
======*******=======
=========靜態分派=========
--B--在處理---Element-A--物件
--ConcreteElementA-- 的方法--myfuncA--呼叫
--B--在處理---Element-B--物件
--ConcreteElementB-- 的方法--myfuncB--呼叫
=========靜態分派=向上轉型後========
--B--在處理---Element--物件
--B--在處理---Element--物件
=========動態分派=========
--ConcreteElementA-- 的方法--accept--呼叫
--B--在處理---Element-A--物件
--ConcreteElementA-- 的方法--myfuncA--呼叫
--ConcreteElementB-- 的方法--accept--呼叫
--B--在處理---Element-B--物件
--ConcreteElementB-- 的方法--myfuncB--呼叫
==================


說明:

方法的過載是靜態多型,編譯期就已經確定執行哪一個方法,自繼承的方法重寫 是動態多型,執行時呼叫的它實際型別的方法。

所以,可以看到,如果直接visit  ConcreteElementA、ConcreteElementB,結果與期望的一致。但是,如果對 ConcreteElementA、ConcreteElementB上轉型到 Element,然後再呼叫,就會出現期望之外的結果,這就是 方法過載靜態分派的結果,他只認當前物件的當前型別。

而繼承多型,會在呼叫方法是,找到他實際型別所對應的方法,如MyVisitorB 上轉型為IVisitor,但在呼叫 visit(Element element) 這個方法時,他會找到當前物件,實際型別的方法(重寫後的方法),讓後在引數的選擇上,卻只認當前型別,所以有“--B--在處理---Element--物件” 這樣的情況。

而 訪問者模式中 accept方法,就是先運用 繼承的動態分派找到 Element物件的實際型別,然後 把當前Element物件的實際型別作為引數 直接給 visitor的visit()方法,以確定實際的正確的引數型別。

如果直接使用visitor visit,那麼在使用的時候,必須清楚的知道該元素的型別,不能通過向上轉型的多型去確定實際型別,如IVisitor 中,若沒有 visit(Element element) 這個方法的存在,在資料集中讀取到一系列的Element 物件後,卻無法直接使用visitor.visit()方法,必須清楚的知道這些Element 的具體型別。

所以,多型有他的好處和方便,但是對於方法過載的多型問題,還是得小心留意些。雙重分派實現的動態分派可以很好的解決這個問題,也可避免instanceof型別判斷的使用。