Java 的雙重分發(fā)與 Visitor 模式實(shí)例詳解
雙重分發(fā)(Double Dispatch)
什么是雙重分發(fā)?
談起面向?qū)ο蟮某绦蛟O(shè)計(jì)時(shí),常說(shuō)起的面向?qū)ο蟮摹付鄳B(tài)」,其中關(guān)于多態(tài),經(jīng)常有一個(gè)說(shuō)法是「父類引用指向子類對(duì)象」。
這種父類的引用指向子類對(duì)象的寫法類似下面這種:
Animal animal = new Dog(); animal.bark();
另一種常用的形式是
public class Keeper { public void say(Animal a) { System.out.println("Animal say"); } public void say(Dog dog) { System.out.println("dog say"); } } Animal animal = new Animal(); Animal dog = new Dog(); Keeper keeper = new Keeper(); keeper.say(animal); keep.say(dog);
那上面的keeper調(diào)用兩次say的時(shí)候,會(huì)輸出什么內(nèi)容呢?會(huì)調(diào)用到兩個(gè)不同的方法嗎?
實(shí)際上在這兩次調(diào)用的時(shí)候,都會(huì)調(diào)用到say(Animal a)這個(gè)方法。由于這些內(nèi)容在編譯期就能確實(shí)下來(lái),這就是 Java 的 靜態(tài)分發(fā)。
從上面的圖我們看到,對(duì)于兩次調(diào)用生成的字節(jié)碼,確實(shí)都指向了say(Animal a)這個(gè)方法,運(yùn)行時(shí)直接執(zhí)行方法,輸出了對(duì)應(yīng)的內(nèi)容。
那對(duì)應(yīng)的animal.bark() 為什么最終會(huì)調(diào)用到 dog 類的方法?這是在運(yùn)行時(shí)確定具體的方法接收者的類型并執(zhí)行。這就是所謂的動(dòng)態(tài)分發(fā),在運(yùn)行時(shí)確定具體的方法,實(shí)現(xiàn)面向?qū)ο蟮亩鄳B(tài)。
分發(fā)(Dispatch)
分發(fā)就是指最終確定一個(gè)要執(zhí)行的方法的過(guò)程。
對(duì)于 Java 等靜態(tài)語(yǔ)言來(lái)說(shuō),都是通過(guò) 單一分發(fā)(Single Dispatch)來(lái)進(jìn)行的方法執(zhí)行。
比如這樣一行代碼
dog.eat(new Bone())
最終執(zhí)行要選擇的eat方法,只會(huì)根據(jù)dog的具體類型選擇到對(duì)應(yīng)的方法,而傳入的參數(shù)并不能影響到對(duì)應(yīng)方法的選擇,這種就是 single Dispatch
為了讓傳入的真實(shí)參數(shù),這里就是Bone來(lái)真正起到作用,就需要用到Double Dispatch或者叫做Multiple Dispatch
也就是說(shuō)最終決定調(diào)用方法是哪一個(gè)的,不僅僅是方法的接收者,還受參數(shù)類型的決定。
Visitor 模式
在GoF 的設(shè)計(jì)模式中,Visitor 模式就使用到了Double Dispatch 達(dá)到了調(diào)用真實(shí)對(duì)象的目的。
對(duì)于Visitor 模式,最常用的例子是樹的遍歷。比如在處理到節(jié)點(diǎn)和樹葉時(shí)的方式有區(qū)別,此歸通過(guò) visitor 的雙重分發(fā),實(shí)現(xiàn)對(duì)于不同的 Element ,執(zhí)行不同的內(nèi)容。
代碼類似這樣:
node.accept(new ConcreateVisitor()); leaf.accept(new ConcreateVisitor());
node 中的 accept方法,會(huì)將自己的真實(shí)類型再次傳遞回visitor
public void accept(Visitor v) { v.visit(this); }
此時(shí),在visitor中,就能根據(jù)真實(shí)的類型來(lái)調(diào)用具體的方法,對(duì)應(yīng)node 和 leaf 分別有類似這樣的方法:
public void visit(Node n); public void visit(Leaf l);
Visitor 總結(jié)起來(lái)一般是包含 visitor 接口,在visitor 接口中,包含各個(gè)即將被訪問(wèn)的 Element對(duì)象的處理邏輯。在 各個(gè)Element 的具體實(shí)現(xiàn)中,再將自己的類型傳遞回visitor 進(jìn)行二次分發(fā),實(shí)現(xiàn)確切邏輯的調(diào)用。
在Tomcat中的應(yīng)用
Visitor 在Tomcat中也有應(yīng)用,典型的是解析EL表達(dá)式。
比如org.apache.el.parser.Node
這個(gè)類中包含一個(gè)accept(NodeVisitor visitor)方法
實(shí)際的 Node 類型有很多,但在真實(shí)調(diào)用的這個(gè)時(shí)候,會(huì)通過(guò)
public void accept(NodeVisitor visitor) throws Exception { visitor.visit(this);
將真實(shí)類型傳回visitor, vistor中會(huì)調(diào)用具體的方法,從而實(shí)現(xiàn)參數(shù)也能起到?jīng)Q定作用的功能。
public void visit(Node node) throws ELException { if (node instanceof AstFunction) { AstFunction funcNode = (AstFunction) node; Method m = null; } else if (xxx) { }
這里一般會(huì)聲明多個(gè)visit方法,然后上面的visit(this)會(huì)直接定位到目標(biāo)方法上。
以上就是 Java 中的各類分發(fā),以及 visitor這種模式通過(guò)模式的形式來(lái)實(shí)現(xiàn)雙重分發(fā)的效果。希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)歡迎給我留言,小編會(huì)及時(shí)回復(fù)大家的!
- C#設(shè)計(jì)模式之Visitor訪問(wèn)者模式解決長(zhǎng)隆歡樂(lè)世界問(wèn)題實(shí)例
- 實(shí)例講解iOS應(yīng)用的設(shè)計(jì)模式開(kāi)發(fā)中的Visitor訪問(wèn)者模式
- 學(xué)習(xí)php設(shè)計(jì)模式 php實(shí)現(xiàn)訪問(wèn)者模式(Visitor)
- Java設(shè)計(jì)模式之訪問(wèn)模式(Visitor者模式)介紹
- php設(shè)計(jì)模式 Visitor 訪問(wèn)者模式
- C++的命名空間詳解
- C++基于OpenCV實(shí)現(xiàn)手勢(shì)識(shí)別的源碼
- C++內(nèi)存模型和名稱空間詳解
- 淺談 C++17 里的 Visitor 模式
相關(guān)文章
Jenkins安裝多個(gè)jdk版本并在項(xiàng)目中選擇對(duì)應(yīng)jdk版本
在使用jenkins構(gòu)建項(xiàng)目時(shí)會(huì)遇到不同的job需要配置不同版本的jdk,下面這篇文章主要給大家介紹了關(guān)于Jenkins安裝多個(gè)jdk版本并在項(xiàng)目中選擇對(duì)應(yīng)jdk版本的相關(guān)資料,需要的朋友可以參考下2024-03-03詳解java中finalize的實(shí)現(xiàn)與相應(yīng)的執(zhí)行過(guò)程
在常規(guī)的java書籍中,即會(huì)描述 object的finalize方法是用于一些特殊的對(duì)象在回收之前再做一些掃尾的工作,但是并沒(méi)有說(shuō)明此是如何實(shí)現(xiàn)的.本篇從java的角度(不涉及jvm以及c++),有需要的朋友們可以參考借鑒。2016-09-09idea創(chuàng)建包含多個(gè)springboot module的maven project的方法
這篇文章主要介紹了idea創(chuàng)建包含多個(gè)springboot module的maven project的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Java基礎(chǔ)知識(shí)精通數(shù)組的使用
數(shù)組對(duì)于每一門編程語(yǔ)言來(lái)說(shuō)都是重要的數(shù)據(jù)結(jié)構(gòu)之一,當(dāng)然不同語(yǔ)言對(duì)數(shù)組的實(shí)現(xiàn)及處理也不盡相同。Java?語(yǔ)言中提供的數(shù)組是用來(lái)存儲(chǔ)固定大小的同類型元素2022-04-04解決idea啟動(dòng)報(bào)錯(cuò)javax.imageio.IIOException的問(wèn)題
這篇文章主要介紹了idea啟動(dòng)報(bào)錯(cuò)javax.imageio.IIOException,解決打不開(kāi)idea問(wèn)題,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09SpringBoot生成PDF的五種實(shí)現(xiàn)方法總結(jié)
這篇文章主要介紹了SpringBoot生成PDF的五種實(shí)現(xiàn)方法,在開(kāi)發(fā)中經(jīng)常會(huì)遇到需要進(jìn)行對(duì)一些數(shù)據(jù)進(jìn)行動(dòng)態(tài)導(dǎo)出PDF文件,然后讓用戶自己選擇是否需要打印出來(lái),這篇文章我們來(lái)介紹五種實(shí)現(xiàn)方法,需要的朋友可以參考下2024-10-10Spring技巧之如何動(dòng)態(tài)讀取配置文件
這篇文章主要介紹了Spring技巧之如何動(dòng)態(tài)讀取配置文件的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09SpringBoot集成P6Spy實(shí)現(xiàn)SQL日志的記錄詳解
P6Spy是一個(gè)框架,它可以無(wú)縫地?cái)r截和記錄數(shù)據(jù)庫(kù)活動(dòng),而無(wú)需更改現(xiàn)有應(yīng)用程序的代碼。一般我們使用的比較多的是使用p6spy打印我們最后執(zhí)行的sql語(yǔ)句2022-11-11