複製鏈接
請複製以下鏈接發送給好友

鴨子類型

鎖定
鴨子類型(英語:duck typing)在程序設計中是動態類型的一種風格
中文名
鴨子類型
外文名
Duck typing
所屬領域
程序設計
類    型
動態類型和某些靜態類型

鴨子類型簡介

程序設計中,鴨子類型(英語:duck typing)是動態類型的一種風格。在這種風格中,一個對象有效的語義,不是由繼承自特定的類或實現特定的接口,而是由"當前方法和屬性的集合"決定。這個概念的名字來源於由James Whitcomb Riley提出的鴨子測試(見下面的“歷史”章節),“鴨子測試”可以這樣表述:
  • “當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。”
在鴨子類型中,關注點在於對象的行為,能作什麼;而不是關注對象所屬的類型。例如,在不使用鴨子類型的語言中,我們可以編寫一個函數,它接受一個類型為"鴨子"的對象,並調用它的"走"和"叫"方法。在使用鴨子類型的語言中,這樣的一個函數可以接受一個任意類型的對象,並調用它的"走"和"叫"方法。如果這些需要被調用的方法不存在,那麼將引發一個運行時錯誤。任何擁有這樣的正確的"走"和"叫"方法的對象都可被函數接受的這種行為引出了以上表述,這種決定類型的方式因此得名。
鴨子類型通常得益於"不"測試方法和函數中參數的類型,而是依賴文檔、清晰的代碼和測試來確保正確使用。 [1] 

鴨子類型概念樣例

考慮用於一個使用鴨子類型的語言的以下偽代碼:
function calculate(a, b, c) => return (a+b)*c

example1 = calculate (1, 2, 3)
example2 = calculate ([1, 2, 3], [4, 5, 6], 2)
example3 = calculate ('apples ', 'and oranges, ', 3)

print to_string example1
print to_string example2
print to_string example3
在樣例中,每次對calculate的調用都使用的對象(數字、列表和字符串)在繼承關係中沒有聯繫。只要對象支持“+”和“*”方法,操作就能成功。例如,翻譯成RubyPython語言,運行結果應該是:
9
[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
apples and oranges, apples and oranges, apples and oranges, 
這樣,鴨子類型在不使用繼承的情況下使用了多態。唯一的要求是calculate函數需要作為參數的對象擁有“+”和“*”方法。以下樣例(Python語言)體現了鴨子測試。就in_the_forest函數而言,對象是一個鴨子:
class Duck:
 2     def quack(self):
 3         print "這鴨子在呱呱叫"
 4 
 5     def feathers(self):
 6         print "這鴨子擁有白色與灰色羽毛"
 7 
 8 
 9 class Person:
10     def quack(self):
11         print "這人正在模仿鴨子"
12 
13     def feathers(self):
14         print "這人在地上拿起1根羽毛然後給其他人看"
15 
16 
17 def in_the_forest(duck):
18     duck.quack()
19     duck.feathers()
20 
21 
22 def game():
23     donald = Duck()
24     john = Person()
25     in_the_forest(donald)
26     in_the_forest(john)
27 
28 
29 game()

鴨子類型鴨子語言類型

一些通常的靜態語言如Boo和C#第四版,有一些額外的類型註解,它們指示編譯器將類的類型檢查安排在運行時而不是編譯時,並在編譯器的輸出中包含用於運行時類型檢查的代碼。這些附加的內容允許這些語言享受鴨子類型的大多數益處,僅有的缺點是需要在編譯時識別和指定這些動態類。

鴨子類型比較

鴨子類型結構類型系統

鴨子類型和結構類型相似但與之不同。結構類型由類型的結構決定類型的兼容性和等價性,而鴨子類型只由結構中在運行時所訪問的部分決定類型的兼容性。Objective Caml語言使用結構類型系統。

鴨子類型接口

接口可以提供鴨子類型的一些益處,但鴨子類型與之不同的是沒有顯式定義任何接口。例如,如果一個第三方Java庫實現了一個用户不允許修改的類,用户就無法把這個類的實例用作一個自己定義的接口的實現,而鴨子類型允許這樣做。

鴨子類型模板或泛型

模板函數或方法在一個靜態類型上下文中應用鴨子測試;這同時帶來了靜態和動態類型檢查的一般優點和缺點。同時,由於在鴨子類型中,只有“在運行時被實際調用的”方法需要被實現,而模板要求實現“在編譯時不能證明不可到達的”所有方法,因此鴨子類型更具有可伸縮性。
實例包括帶有模板的C++語言和Java語言的泛型。 [2] 

鴨子類型批評

關於鴨子類型常常被引用的一個批評是它要求程序員在任何時候都必須很好地理解他/她正在編寫的代碼。在一個強靜態類型的、使用了類型繼承樹和參數類型檢查的語言中,給一個類提供未預測的對象類型更為困難。例如,在Python中,你可以創建一個稱為Wine的類,並在其中需要實現press方法。然而,一個稱為Trousers的類可能也實現press()方法。為了避免奇怪的、難以檢測的錯誤,開發者在使用鴨子類型時需要意識到每一個“press”方法的可能使用,即使在語義上和他/她所正在編寫工作的代碼沒有任何關係。
本質上,問題是:“如果它走起來像鴨子並且叫起來像鴨子”,它也可以是一隻正在模仿鴨子的龍。儘管它們可以模仿鴨子,但也許你不總是想讓龍進入池塘。
鴨子類型的提倡者,如吉多·範羅蘇姆,認為這個問題可以通過在測試和維護代碼庫前擁有足夠的瞭解來解決。
對鴨子類型的批評傾向於成為關於動態類型和靜態類型的爭論的更廣闊的觀點的特殊情形。 [2] 

鴨子類型發展歷史

Alex Martelli很早(2000年)就在發佈到comp.lang.python新聞組上的一則消息中使用了這一術語。他同時對鴨子測試的錯誤的字面理解提出了提醒,以避免人們錯誤認為這個術語已經被使用。
  • “換言之,不要檢查它是不是一個鴨子:檢查它像不像一個鴨子地,等等。取決於你需要哪個像鴨子的行為的子集來使用語言。” [2] 

鴨子類型實現

  • 在ColdFusion中
web應用程序腳本語言ColdFusion允許函數參數被指定為類型為any。對於這種參數,任意對象都可被傳入,函數調用在運行時被動態綁定。如果對象沒有實現一個被調用的函數,一個可被捕獲並優雅地處理的運行時異常將被拋出。在ColdFusion 8中,這也可以被一個已定義的事件onMissingMethod()而不是異常處理器處理。另一個可替代的參數類型WEB-INF.cftags.component限制傳入參數是一個ColdFusion組件(CFC),在一個不正確的對象傳入時它提供了更好的錯誤消息。 [2] 

鴨子類型參見

  • 鴨子測試(duck test)
參考資料
  • 1.    Heim, Michael.Exploring Indiana Highways.美國·明尼蘇達州·瓦巴肖:Travel Organization Network Exchang,Inc,2007:68
  • 2.    曾探 .《JavaScript設計模式與開發實踐》.北京:人民郵電出版社,2015-05:2-3