Java 的雙重分發(fā)與 Visitor 模式
雙重分發(fā)(Double Dispatch)
什么是雙重分發(fā)?
談起面向?qū)ο蟮某绦蛟O(shè)計時,常說起的面向?qū)ο蟮摹付鄳B(tài)」,其中關(guān)于多態(tài),經(jīng)常有一個說法是「父類引用指向子類對象」。
這種父類的引用指向子類對象的寫法類似下面這種:
- 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的時候,會輸出什么內(nèi)容呢?會調(diào)用到兩個不同的方法嗎?
實際上在這兩次調(diào)用的時候,都會調(diào)用到say(Animal a)這個方法。由于這些內(nèi)容在編譯期就能確實下來,這就是 Java 的 靜態(tài)分發(fā)。
從上面的圖我們看到,對于兩次調(diào)用生成的字節(jié)碼,確實都指向了say(Animal a)這個方法,運行時直接執(zhí)行方法,輸出了對應(yīng)的內(nèi)容。
那對應(yīng)的animal.bark() 為什么最終會調(diào)用到 dog 類的方法?這是在運行時確定具體的方法接收者的類型并執(zhí)行。這就是所謂的動態(tài)分發(fā),在運行時確定具體的方法,實現(xiàn)面向?qū)ο蟮亩鄳B(tài)。
分發(fā)(Dispatch)
分發(fā)就是指最終確定一個要執(zhí)行的方法的過程。
對于 Java 等靜態(tài)語言來說,都是通過 單一分發(fā)(Single Dispatch)來進行的方法執(zhí)行。
比如這樣一行代碼
- dog.eat(new Bone())
最終執(zhí)行要選擇的eat方法,只會根據(jù)dog的具體類型選擇到對應(yīng)的方法,而傳入的參數(shù)并不能影響到對應(yīng)方法的選擇,這種就是 single Dispatch
為了讓傳入的真實參數(shù),這里就是Bone來真正起到作用,就需要用到Double Dispatch或者叫做Multiple Dispatch
也就是說最終決定調(diào)用方法是哪一個的,不僅僅是方法的接收者,還受參數(shù)類型的決定。
Visitor 模式
在GoF 的設(shè)計模式中,Visitor 模式就使用到了Double Dispatch 達(dá)到了調(diào)用真實對象的目的。
對于Visitor 模式,最常用的例子是樹的遍歷。比如在處理到節(jié)點和樹葉時的方式有區(qū)別,此歸通過 visitor 的雙重分發(fā),實現(xiàn)對于不同的 Element ,執(zhí)行不同的內(nèi)容。
代碼類似這樣:
- node.accept(new ConcreateVisitor());
- leaf.accept(new ConcreateVisitor());
node 中的 accept方法,會將自己的真實類型再次傳遞回visitor
- public void accept(Visitor v) {
- v.visit(this);
- }
此時,在visitor中,就能根據(jù)真實的類型來調(diào)用具體的方法,對應(yīng)node 和 leaf 分別有類似這樣的方法:
- public void visit(Node n);
- public void visit(Leaf l);
Visitor 總結(jié)起來一般是包含 visitor 接口,在visitor 接口中,包含各個即將被訪問的 Element對象的處理邏輯。在 各個Element 的具體實現(xiàn)中,再將自己的類型傳遞回visitor 進行二次分發(fā),實現(xiàn)確切邏輯的調(diào)用。
在Tomcat中的應(yīng)用
Visitor 在Tomcat中也有應(yīng)用,典型的是解析EL表達(dá)式。
比如org.apache.el.parser.Node
這個類中包含一個accept(NodeVisitor visitor)方法
實際的 Node 類型有很多,但在真實調(diào)用的這個時候,會通過
- public void accept(NodeVisitor visitor) throws Exception {
- visitor.visit(this);
將真實類型傳回visitor, vistor中會調(diào)用具體的方法,從而實現(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) {
- }
這里一般會聲明多個visit方法,然后上面的visit(this)會直接定位到目標(biāo)方法上。
以上就是 Java 中的各類分發(fā),以及 visitor這種模式通過模式的形式來實現(xiàn)雙重分發(fā)的效果。
【本文為51CTO專欄作者“侯樹成”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號『Tomcat那些事兒』獲取授權(quán)】