分派(dispatch) 是指按照對象的實際類型爲其綁定對應方法體的過程。html
例若有X類及其兩個子類X一、X2,它們都實現了實例方法m()——一般子類X一、X2的方法前應該加@Override,因此有3個m()。程序員
對於消息表達式a.m(b,c),按照一個對象的實際類型綁定對應方法體,稱爲單分派。固然,這個「一個對象」比較特殊,每個消息表達式a.m(b,c)只有一個消息接收者,這個「一個對象」就是指消息接收者,即a.m(b, c)中的a。因此,僅按照消息接收者的實際類型綁定實際類型提供的方法體,即單分派(singledispatch),就是面向對象中的動態綁定!編程
假設對於消息表達式a.m(b,c),若是可以按照a、b和c的實際類型爲其綁定對應方法體,則稱爲三分派。簡單起見,研究雙分派(double dispatch)就夠了。設計模式
所謂的雙分派,則是但願a.foo(b)可以 ①按照a的實際類型綁定其override的方法體,並且可以 ②按照b的實際類型綁定其重載的方法即foo(Y)、foo(Y1)、foo(Y2)中的適當方法體。 【相關概念,能夠參考《設計模式.5.11訪問者模式》p223】bash
遺憾的是,Java不支持雙分派。對於foo(X)、foo(X1)和foo(X2)這些重載的方法,Java在編譯時,就爲foo(b)按照b的聲明類型靜態綁定了foo(X)這個的方法體,而不會去判斷b的實際類型是X1仍是X2。 Java中可使用運行時類型識別(Run-Time TypeIdentification、RTTI)技術,即便用關鍵字instanceof判斷實際類型。雖然聲明類型爲父類Y,程序中按照實際類型從新聲明temp,並將參數向下造型。RTTI雖然代碼簡潔,但使用分支語句不夠優雅。另外,①程序員還要注意,具體類型判斷在前;②RTTI將佔用較多的運行時間和空間。ide
《Java編程思想》中,有句話ui
Java中除了static方法和final方法(private方法屬於final方法)以外,其餘全部方法都是後期綁定,也就是運行時綁定,咱們沒必要判斷是否應該進行後期綁定-它會自動發生。this
這裏提到的後期綁定,也只是針對參數的聲明類型來選擇具體的方法。spa
既然Java支持a.m(b)時,按a的具體類型綁定相應的方法,那若是經過某種方式在a.m(b)的實現中,完成了b.m1()的調用,那不就實現「雙分派」了嗎?縱覽GOF23,有兩種設計模式完美地支持這一種,分別是命令模式和訪問者模式設計
命令模式的UML圖
先說個題外話,相信你們從UML圖中能夠看出些問題,爲何Client既須要知道Invoker又須要知道Receiver呢,Invoker角色接受Client的命令並執行命令,而真正命令的實施者是Receiver,用《設計模式之禪》裏的比方,Invoker是項目經理,Receiver就是幹活的碼農或美工等等。依照迪米特法則,那Client就不該該知道Receiver。這個問題咱們放在後面討論。下面先看代碼:抽象receiver
public abstract class Receiver {
abstract void doSth();
}
複製代碼
ConcreteReceiver1
public class ConcreteCommand1 extends Command {
private Receiver receiver;
public ConcreteCommand1(Receiver receiver) {
this.receiver = receiver;
}
@Override
void execute(Receiver receiver) {
System.out.println("我是command1, 入參是Receiver");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver1 receiver) {
System.out.println("我是command1, 入參是ConcreteReceiver1");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver2 receiver) {
System.out.println("我是command1, 入參是ConcreteReceiver2");
receiver.doSth();
}
}
複製代碼
抽象Command
public abstract class Command {
private Receiver receiver;
public Command(Receiver receiver) {
this.receiver = receiver;
}
abstract void execute(Receiver receiver);
abstract void execute(ConcreteReceiver1 receiver);
abstract void execute(ConcreteReceiver2 receiver);
public Receiver getReceiver() {
return receiver;
}
}
複製代碼
ConcreteCommand1
public class ConcreteCommand1 extends Command {
public ConcreteCommand1(Receiver receiver) {
super(receiver);
}
@Override
public Receiver getReceiver() {
return super.getReceiver();
}
@Override
void execute(Receiver receiver) {
System.out.println("我是command1, 入參是Receiver");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver1 receiver) {
System.out.println("我是command1, 入參是ConcreteReceiver1");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver2 receiver) {
System.out.println("我是command1, 入參是ConcreteReceiver2");
receiver.doSth();
}
}
複製代碼
ConcreteCommand2
public class ConcreteCommand2 extends Command {
public ConcreteCommand2(Receiver receiver) {
super(receiver);
}
@Override
void execute(Receiver receiver) {
System.out.println("我是command2, 入參是Receiver");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver1 receiver) {
System.out.println("我是command2, 入參是ConcreteReceiver1");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver2 receiver) {
System.out.println("我是command2, 入參是ConcreteReceiver2");
receiver.doSth();
}
@Override
public Receiver getReceiver() {
return super.getReceiver();
}
}
複製代碼
Invoker
public class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void act() {
this.command.execute(command.getReceiver());
}
}
複製代碼
client
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker();
Receiver receiver1 = new ConcreteReceiver1();
Receiver receiver2 = new ConcreteReceiver2();
Command command1 = new ConcreteCommand1(receiver1);
Command command2 = new ConcreteCommand2(receiver2);
invoker.setCommand(command1);
invoker.act();
}
}
複製代碼
執行結果
我是command1, 入參是Receiver
receiver1 處理命令1
複製代碼
爲了湊a.m(b)這種格式,弄得很醜,你們見諒。Client中,Receiver和Command都是按接口聲明的,當執行到invoker.setCommand(command1); invoker.act();
時,程序走至
ConcreteCommand1.execute(Receiver);
,所以Java綁定了ConcreteCommand1中的第一個方法
咱們看到的打印輸出就是
我是command1, 入參是Receiver
在這個方法中,receiver又成爲了a.m(b)
中的a,由於Java又能夠根據其實際類型進行方法綁定,所以跑到ConcreteReceiver1中,爲了避免要臉地硬捧a.m(b),這裏又羅哩羅嗦地寫了三個重載方法。這時b是ConcreteCommand1,所以找到重載方法
receiver1 處理命令1
《設計模式之禪》也提到了我開篇的疑惑,Client爲何要知曉Receiver的存在呢?事實上咱們在實際工做中,沒有人真的那麼幹。引用一段書中的原文:
每個模式到實際應用的時候都有一些變形,命令模式的Receiver在實際應用中通常都會被封裝掉(除非很是必要,例如撤銷處理),那是由於在項目中:約定的優先級最高,每個命令是對一個或多個Receiver的封裝,咱們能夠在項目中經過有意義的類名或命令名處理命令角色和接收者角色的耦合關係(這就是約定),減小高層模塊(Client類)對低層模塊(Receiver角色類)的依賴關係,提升系統總體的穩定性。所以,建議你們在實際的項目開發時採用封閉Receiver的方式(固然了,仁者見仁,智者見智),減小Client對Reciver的依賴。
瞭解訪問者模式的朋友看到這確定會說,這tm哪裏是命令模式啊,明明是披着命令模式皮的訪問者模式嘛!確實,爲了千方百計說明雙分派,已經把命令模式搞變態了,不妨好好看下訪問者模式。舉《設計模式之禪》的例子
演員演電影角色,一個演員能夠扮演多個角色,咱們先定義一個影視中的兩個角色:功夫主角和白癡配角
public interface Role {
//演員要扮演的角色
}
public class KungFuRole implements Role {
//武功天下第一的角色
}
public class IdiotRole implements Role {
//一個弱智角色
}
複製代碼
角色有了,咱們再定義一個演員抽象類
public abstract class AbsActor {
//演員都可以演一個角色
public void act(Role role){
System.out.println("演員能夠扮演任何角色");
}
//能夠演功夫戲
public void act(KungFuRole role){
System.out.println("演員均可以演功夫角色");
}
}
複製代碼
很簡單,這裏使用了Java的重載,咱們再來看青年演員和老年演員,採用覆寫的方式來 細化抽象類的功能
public class YoungActor extends AbsActor {
//年輕演員最喜歡演功夫戲
public void act(KungFuRole role){
System.out.println("最喜歡演功夫角色");
}
}
public class OldActor extends AbsActor {
//不演功夫角色
public void act(KungFuRole role){
System.out.println("年齡大了,不能演功夫角色");
}
}
複製代碼
覆寫和重載都已經實現,咱們編寫一個場景,
public class Client {
public static void main(String[] args) {
//定義一個演員
AbsActor actor = new OldActor();
//定義一個角色
Role role = new KungFuRole();
//開始演戲
actor.act(role);
actor.act(new KungFuRole());
}
}
複製代碼
獲得輸出結果
演員能夠扮演任何角色
年齡大了,不能演功夫角色
複製代碼
使用上節提到的介紹的方法,能夠很是輕鬆地分析出雙分派的實現原理。
你們能夠發現,經過設計模式實現的雙分派,實際上是「僞雙分派」,至少深層的原理,須要閱讀更多資料,等我讀完《深刻理解Java虛擬機》後,會回來把這一節補上。
www.iteye.com/topic/11307… www.voidcn.com/article/p-d… 《設計模式之禪》 《Java編程思想》