如果一隻動物走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻動物就可以被稱為鴨子。
許多程式語言都支援 Duck Typing ,通常 Duck Typing 是動態程式語言用來實現多型的一種方式。
在理解 Duck Typing 前,先看一張圖片,這是曾經一度很火的大黃鴨
先問一個比較考三觀的問題:圖片中的大黃鴨,它是不是一隻鴨子呢?
這個問題,得看你從哪個角度去看,如果從人們常識的認知中的角度去看,它顯然不是一隻鴨子,因為它連最基本的生命都沒有。
但是從 Duck Typing 的角度來看,它就是一隻鴨子!
Duck Typing 的原話是,走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼它就是一隻鴨子。
這個原話是可以靈活理解的,就看我們怎麼定義鴨子的行為,我們可以說,能浮在水上游的,黃色的,可愛的就是鴨子,那麼,圖片中的大黃鴨,它就是一隻鴨子!
這就是所謂的 Duck Typing,它只關心事物的外部行為而非內部結構。它並不關心你這隻鴨子是長肉的還是充氣的。
在程式設計中,也常常用這種方式來描述事物。那麼不同的程式語言中,Duck Typing 是怎麼樣實現的呢?
先看一個函式:
def download(fetcher):
return fetcher.get("http://xxx");
有一個 download 函式,傳過來一個 fetcher 引數,fetcher 是可以獲取一個 url 連結的資源的。
這個 fetcher 就是一個 Duck Typing 的物件,使用者約定好這個 fetcher 會有一個 get 函式就可以了。
顯然這個 download 函式會有以下問題:
執行時才知道傳入的 fetcher 有沒有 get 函式。那麼站在 download 函式的使用者的角度上看,我怎麼知道需要給 fetcher 實現 get 方法呢?我不可能去閱讀 download 函式的程式碼,實際情況中,可能 download 函式的程式碼很長,可能 fetcher 不只要實現 get 方法,還有其它方法需要實現。通常這種情況需要通過加註釋來說明。
C++ 不是動態語言,但是它也能支援 Duck Typing,它是通過模板來支援的。
示例程式碼:
template <class F>
string download(const F& fetcher){
return fetcher.get("http://xxxx")
}
這段程式碼與 Python 的實現方法類似,這個 fetcher 隨便什麼型別都可以,只要實現一個 get 方法,就能通過編譯。
那麼這種實現方法有什麼缺點呢,就是,編譯時,才知道傳入的 fetcher 有沒有 get 方法。
但它比 python 好一點了,python 是執行時才知道,C++ 是編譯時就知道。
同樣,這種情況,還是需要註釋來說明。
Java 沒有 Duck Typing,它只有類似的程式碼。Java 的 duck typing :
<F extends FetcherInterface>
String download(F fetcher){
return fetcher.get("http://xxxx")
}
它同樣也用了模板型別。模板 F 必須 extends FetcherInterface ,有了這個限定,就能逼著 download 函式的使用者對 fetcher 實現 get 方法,它解決了需要註釋來說明的缺點。
傳入的引數必須實現 FetcherInterface 介面,就沒有執行時發現錯誤,編譯時發現錯誤的問題。
但是,它嚴格上來說不是 Duck Typing 。
如果 download 函式只依賴 fetcher 的 get 方法,而 FetcherInterface 介面必須要實現除 get 方法以外,還有其它方法,那麼也要一一實現,非常不靈活。
在 Java 的 Duck Typing 類似程式碼中,如果 fetcher 引數需要同時實現兩個或以上的介面方法時,Java 是沒有辦法做到的。但 Go 語言可以做到。
type Fetcher interface {
Get(url string) string
}
type Saver interface {
Save(content string)
}
type FetcherAndSaver interface {
Fetcher
Saver
}
func download(f Fetcher) string {
return f.Get("http://xxxx")
}
func save(f saver) {
f.Save("some thing")
}
func downloadAndSave(f FetcherAndSaver) {
content := f.Get("http://xxxx")
f.Save(content)
}
# 實現者
type MyFetcherAndSaver struct {
}
func (f MyFetcherAndSaver) Get(url string) string {
...
}
func (f MyFetcherAndSaver) Save(content string) {
...
}
func main() {
f := MyFetcherAndSaver{}
download(f)
save(f)
downloadAndSave(f)
}
這裡定義了三個介面,只要有 Get 方法的就是 Fetcher,只要有 Save 方法的就是 Saver,同時有 Get 方法和 Save 方法就是 FetcherAndSaver 。
實現者 MyFetcherAndSaver 並不需要宣告它實現了哪些介面,只要它有相關介面的所定義的方法,那麼它的例項,就即能作為 Fetcher 介面來使用,又能作為 Saver 介面來使用,也能作為 FetcherAndSaver 介面來使用。
Go 的實現方法相對比較靈活,又不失型別檢查。總的來說,特點有:
- 即能同時實現多個介面
- 又具有 python , C++ 的 Duck Typing 靈活性
- 又具有 java 的型別檢查。
看看自己是不是一個靠譜的程式設計師,來做題試試。https://job.xyh.io