鴨子型別在程式設計中是動態型別的一種風格。在這種風格中，一個物件有效的語義，不是由繼承自特定的類或實現特定的介面，而是由「當前方法和屬性的集合」決定。這個概念的名字來源於由提出的鴨子測試（見下面的 歷史 章節），鴨子測試 可以這樣表述： 當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子，那麼這隻鳥就可以被稱為鴨子。 在鴨子型別中，關注點在於物件的行為，能做什麼；而不是關注物件所屬的型別。例如，在不使用鴨子型別的語言中，我們可以編寫一個函式，它接受一個型別為「鴨子」的物件，並呼叫它的「走」和「叫」方法。在使用鴨子型別的語言中，這樣的一個函式可以接受一個任意型別的物件，並呼叫它的「走」和「叫」方法。如果這些需要被呼叫的方法不存在，那麼將引發一個執行時錯誤。任何擁有這樣的正確的「走」和「叫」方法的物件都可被函式接受的這種行為引出了以上表述，這種決定型別的方式因此得名。 鴨子型別通常得益於「不」測試方法和函式中引數的型別，而是依賴文件、清晰的程式碼和測試來確保正確使用。 在常規型別中，我們能否在一個特定場景中使用某個物件取決於這個物件的型別，而在鴨子型別中，則取決於這個物件是否具有某種屬性或者方法 即只要具備特定的屬性或方法，能透過鴨子測試，就可以使用。 概念樣例 考慮用於一個使用鴨子型別的語言的以下虛擬碼： function calculate a b c return abc 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 的呼叫都使用的物件（數字、列表和字串）在繼承關係中沒有聯絡。只要物件支援 和 方法，操作就能成功。例如，翻譯成 Ruby 或 Python 語言，執行結果應該是： 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 def quack self print 這鴨子正在嘎嘎叫 def feathers self print 這鴨子擁有白色和灰色的羽毛 class Person def quack self print 這人正在模仿鴨子 def feathers self print 這人在地上拿起 1 根羽毛然後給其他人看 def in_the_forest duck duckquack duckfeathers def game donald Duck john Person in_the_forest donald in_the_forest john game 靜態語言中的鴨子型別 一些通常的靜態語言如 Boo 和 C 第四版，有一些額外的型別註解，它們指示編譯器將類的型別檢查安排在執行時而不是編譯時，並在編譯器的輸出中包含用於執行時型別檢查的程式碼。這些附加的內容允許這些語言享受鴨子型別的大多數益處，僅有的缺點是需要在編譯時識別和指定這些動態類。 與其他型別系統的比較 結構型別系統 鴨子型別和相似但與之不同。結構型別由型別的結構決定型別的相容性和等價性，而鴨子型別只由結構中在執行時所訪問的部分決定型別的相容性。Objective Caml 語言使用結構型別系統。 介面 介面可以提供鴨子型別的一些益處，但鴨子型別與之不同的是沒有顯式定義任何介面。例如，如果一個第三方 Java 庫實現了一個使用者不允許修改的類，使用者就無法把這個類的例項用作一個自己定義的介面的實現，而鴨子型別允許這樣做。 模板或泛型 模板函式或方法在一個靜態型別上下文中應用鴨子測試；這同時帶來了靜態和動態型別檢查的一般優點和缺點。同時，由於在鴨子型別中，只有 在執行時被實際呼叫的 方法需要被實現，而模板要求實現 在編譯時不能證明的 所有方法，因此鴨子型別更具有可伸縮性。 例項包括帶有模板的 C 語言和 Java 語言的泛型。 批評 關於鴨子型別常常被引用的一個批評是它要求程式設計師在任何時候都必須很好地理解他 她正在編寫的程式碼。在一個強靜態型別的、使用了型別繼承樹和引數型別檢查的語言中，給一個類提供未預測的物件型別更為困難。例如，在 Python 中，你可以建立一個稱為 Wine 的類，並在其中需要實現 press 方法。然而，一個稱為 Trousers 的類可能也實現 press 方法。為了避免奇怪的、難以檢測的錯誤，開發者在使用鴨子型別時需要意識到每一個 press 方法的可能使用，即使在語義上和他 她所正在編寫工作的程式碼沒有任何關係。 本質上，問題是：如果它走起來像鴨子並且叫起來像鴨子，它也可以是一隻正在模仿鴨子的龍。儘管它們可以模仿鴨子，但也許你不總是想讓龍進入池塘。 鴨子型別的提倡者，如吉多範羅蘇姆，認為這個問題可以透過在測試和維護程式碼庫前擁有足夠的瞭解來解決。 對鴨子型別的批評傾向於成為關於動態型別和靜態型別的爭論的更廣闊的觀點的特殊情形。 歷史 Alex Martelli 很早（2000 年）就在釋出到 complangpython 新聞組上的一則訊息 polymorphism was Re Type checking in python 中使用了這一術語。他同時對鴨子測試的錯誤的字面理解提出了提醒，以避免人們錯誤認為這個術語已經被使用。 換言之，不要檢查它是不是一個鴨子：檢查它像不像一個鴨子地叫，等等。取決於你需要哪個像鴨子的行為的子集來使用語言。 實現 在 Common Lisp 中 Common Lisp 提供了一個物件導向的擴充套件（Common Lisp 物件系統，簡寫為 CLOS）。在 Common Lisp 中，CLOS 和 Lisp 的動態型別使鴨子型別成為一種通用的程式設計風格。 使用 Common Lisp，使用者通常不需要查詢型別，因為如果一個函式不適用，系統會丟擲一個執行時錯誤。這個錯誤可以被 Common Lisp 的條件系統處理。在類外定義的方法也可以為指定的物件定義。 defclass duck defmethod quack aduck duck print 這鴨子正在嘎嘎叫 defmethod feathers aduck duck print 這鴨子有白色和灰色羽毛 defclass person defmethod quack aperson person print 這人正在模仿鴨子 defmethod feathers aperson person print 這人在地上拿起 1 根羽毛然後給其他人看 defmethod intheforest duck quack duck feathers duck defmethod game let donald makeinstance duck john makeinstance person intheforest donald intheforest john game Common Lisp 通常的開發風格（像 SLIME 一樣使用 Lisp REPL）也允許互動式修復： defclass cat STANDARDCLASS CAT quack makeinstance cat Error There is no applicable method for the generic function STANDARDGENERICFUNCTION QUACK x300041C2371F when called with arguments CAT x300041C7EEFD If continued Try calling it again 1 defmethod quack acat cat print 這貓正在模仿鴨子 STANDARDMETHOD QUACK CAT 1 continue 這貓正在模仿鴨子 透過這種方法，軟體可以透過擴充套件只有部分工作的使用鴨子型別的程式碼來開發。 在 Python 中 鴨子型別在 Python 中被廣泛使用。Python 術語表 Python 術語表這樣定義鴨子型別： 在 Python 中，鴨子型別的最典型例子就是類似 file 的類。這些類可以實現 file 的一些或全部方法，並可以用於 file 通常使用的地方。例如，GzipFileGzipFile 實現了一個用於訪問 gzip 壓縮的資料的類似 file 的物件。cStringIOcStringIO 允許把一個 Python 字串視作一個檔案。套接字（socket）也和檔案共同擁有許多相同的方法。然而套接字缺少 tell 方法 tell 方法，不能用於 GzipFile 可以使用的所有地方。這體現了鴨子型別的可伸縮性：一個類似 file 的物件可以實現它有能力實現的方法，且只能被用於它有意義的情形下。 EAFP 原則描述了異常處理的使用。例如相對於檢查一個自稱為類似 Duck 的物件是否擁有一個 quack 方法（使用 if hasattr mallard quack ），人們通常更傾向於用異常處理把對 quack 的呼叫嘗試包裹起來： try mallardquack except AttributeError TypeError print mallard 並沒有 quack 函式 這個寫法的優勢在於它鼓勵結構化處理其他來自類的錯誤（這樣的話，例如，一個不能完成 quack 的 Duck 子類可以丟擲一個 QuackException，這個異常可以簡單地新增到包裹它的程式碼，並不需要影響更多的程式碼的邏輯。同時，對於其他不同類的物件存在不相容的成員而造成的命名衝突，它也能夠處理（例如，假設有一個醫學專家 Mallard 有一個布林屬性將他分類為 quackTrue，試圖執行 Mallardquack 將丟擲一個 TypeError）。 在更實際的實現類似 file 的行為的例子中，人們更傾向於使用 Python 的異常處理機制來處理各種各樣的可能因為各種程式設計師無法控制的環境和作業系統問題而發生的 IO 錯誤。在這裡，鴨子型別 產生的異常可以在它們自己的子句中捕獲，與作業系統、IO 和其他可能的錯誤分別處理，從而避開復雜的檢測和錯誤檢查邏輯。 在 C 中 C 40 實現了動態成員查詢（dynamic member lookup）實現了鴨子型別化。注意下例中類方法 InTheForest 的引數型別被宣告為 dynamic。 using System namespace DuckTyping public class Duck public void Quack ConsoleWriteLine 這鴨子正在嘎嘎叫 public void Feathers ConsoleWriteLine 這鴨子擁有白色與灰色羽毛 public class Person public void Quack ConsoleWriteLine 這人正在模仿鴨子 public void Feathers ConsoleWriteLine 這人在地上拿起 1 根羽毛然後給其他人看 internal class Program private static void InTheForest dynamic duck duckQuack duckFeathers private static void Game Duck donald new Duck Person john new Person InTheForest donald InTheForest john private static void Main Game 在 ObjectiveC 中 ObjectiveC，C 和 Smalltalk 的一個交錯，像 Smalltalk 一樣，允許使用者宣告一個物件的型別為 id 並向它傳送任何資訊。傳送者可以測試一個物件以瞭解它能不能對一個訊息響應，物件可以在收到訊息的時候決定響應與否，如果傳送者傳送了一個接收者不能響應的訊息，一個異常會被丟擲。因此，鴨子型別在 ObjectiveC 中被完全支援。 在 ColdFusion 中 web 應用程式指令碼語言 ColdFusion 允許函式引數被指定為型別為 any。對於這種引數，任意物件都可被傳入，函式呼叫在執行時被動態繫結。如果物件沒有實現一個被呼叫的函式，一個可被捕獲並優雅地處理的執行時異常將被丟擲。在 ColdFusion 8 中，這也可以被一個已定義的事件 onMissingMethod 而不是異常處理器處理。另一個可替代的引數型別 WEBINFcftagscomponent 限制傳入引數是一個 ColdFusion 元件（CFC），在一個不正確的物件傳入時它提供了更好的錯誤訊息。 在 JavaScript 中 class Duck constructor thisquack consoleinfo 這鴨子正在嘎嘎叫 thisfeathers consoleinfo 這鴨子擁有白色與灰色羽毛 class Person constructor thisquack consoleinfo 這人正在模仿鴨子 thisfeathers consoleinfo 這人在地上拿起一根羽毛然後給其他人看 function InTheForest duck duckquack duckfeathers function game var donald new Duck var john new Person InTheForest donald InTheForest john game 參考文獻 參閱 鴨子測試 duck test 外部連結 Duck Typing Ruby How to duck type the psychology of static typing in Ruby Python 術語表中的 ducktyping 詞條 Dr Dobbs June 01 2005 Templates and Duck Typing Javascript typeof limitations and duck typing Python from a Java perspective Part 2 How duck typing influences class design and design principles Category 物件導向的程式設計 Category 型別論 Category 帶有虛擬碼示例的條目 Category 帶有程式碼示例的條目