鴨子型別

鴨子型別，“鴨子測試”可以這樣表述：“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子，那麼這隻鳥就可以被稱為鴨子。”在鴨子型別中，關注點在於物件的行為，能做什麼；而不是關注物件所屬的型別。例如，在不使用鴨子型別的語言中，我們可以編寫一個函式，它接受一個型別為「鴨子」的物件，並呼叫它的「走」和「叫」方法。在使用鴨子型別的語言中，這樣的一個函式可以接受一個任意型別的物件，並呼叫它的「走」和「叫」方法。如果這些需要被呼叫的方法不存在，那麼將引發一個執行時錯誤。任何擁有這樣的正確的「走」和「叫」方法的物件都可被函式接受的這種行為引出了以上表述，這種決定型別的方式因此得名。鴨子型別通常得益於「不」測試方法和函式中引數的型別，而是依賴文件、清晰的程式碼和測試來確保正確使用。在常規型別中，我們能否在一個特定場景中使用某個物件取決於這個物件的型別，而在鴨子型別中，則取決於這個物件是否具有某種屬性或者方法——即只要具備特定的屬性或方法，能透過鴨子測試，就可以使用。概念樣例.考慮用於一個使用鴨子型別的語言的以下虛擬碼：function calculate(a, b, c) =&gt; return (a+b)*cexample1 = calculate (1, 2, 3)example2 = calculate ([1, 2, 3], [4, 5, 6], 2)example3 = calculate ('apples ', 'and oranges, ', 3)print to_string example1print to_string example2print to_string example3在樣例中，每次對codice_1的呼叫都使用的物件在繼承關係中沒有聯絡。只要物件支援“+”和“*”方法，操作就能成功。例如，翻譯成Ruby或Python語言，執行結果應該是：9[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]apples and oranges, apples and oranges, apples and oranges,這樣，鴨子型別在不使用繼承的情況下使用了多型。唯一的要求是codice_1函式需要作為引數的物件擁有“+”和“*”方法。以下樣例體現了鴨子測試。就codice_3函式而言，物件是一個鴨子：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):duck.quack()duck.feathers()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很早中使用了這一術語。他同時對鴨子測試的錯誤的字面理解提出了提醒，以避免人們錯誤認為這個術語已經被使用。“換言之，不要檢查它是不是一個鴨子：檢查它像不像一個鴨子地叫，等等。取決於你需要哪個像鴨子的行為的子集來使用語言。”實現.在Common Lisp中.Common Lisp提供了一個面向物件的擴充套件。在Common Lisp中，CLOS和Lisp的動態型別使鴨子型別成為一種通用的程式設計風格。使用Common Lisp，使用者通常不需要查詢型別，因為如果一個函式不適用，系統會丟擲一個執行時錯誤。這個錯誤可以被Common Lisp的條件系統處理。在類外定義的方法也可以為指定的物件定義。(print "這鴨子正在嘎嘎叫"))(print "這鴨子有白色和灰色羽毛"))(print "這人正在模仿鴨子"))(print "這人在地上拿起1根羽毛然後給其他人看"))(quack duck)(feathers duck))(let ((donald (make-instance 'duck))(john (make-instance 'person)))(in-the-forest donald)(in-the-forest john)))Common Lisp通常的開發風格也允許互動式修復：? (defclass cat () ())? (quack (make-instance 'cat))&gt; Error: There is no applicable method for the generic function:&gt; #&gt; when called with arguments:&gt; (#)&gt; If continued: Try calling it again1 &gt; (defmethod quack ((a-cat cat))(print "這貓正在模仿鴨子"))1 &gt; (continue)"這貓正在模仿鴨子"透過這種方法，軟體可以透過擴充套件只有部分工作的使用鴨子型別的程式碼來開發。在Python中.鴨子型別在Python中被廣泛使用。Python術語表這樣定義鴨子型別：在Python中，鴨子型別的最典型例子就是類似file的類。這些類可以實現codice_4的一些或全部方法，並可以用於codice_4通常使用的地方。例如，codice_6實現了一個用於訪問gzip壓縮的資料的類似file的物件。codice_7允許把一個Python字串視作一個檔案。套接字也和檔案共同擁有許多相同的方法。然而套接字缺少codice_8方法，不能用於codice_6可以使用的所有地方。這體現了鴨子型別的可伸縮性：一個類似file的物件可以實現它有能力實現的方法，且只能被用於它有意義的情形下。EAFP原則描述了異常處理的使用。例如相對於檢查一個自稱為類似Duck的物件是否擁有一個"quack()"方法，人們通常更傾向於用異常處理把對"quack"的呼叫嘗試包裹起來：try:mallard.quack()except (AttributeError, TypeError):print("mallard並沒有quack()函式")這個寫法的優勢在於它鼓勵結構化處理其他來自類的錯誤。在更實際的實現類似file的行為的例子中，人們更傾向於使用Python的異常處理機制來處理各種各樣的可能因為各種程式設計師無法控制的環境和作業系統問題而發生的I/O錯誤。在這裡，“鴨子型別”產生的異常可以在它們自己的子句中捕獲，與作業系統、I/O和其他可能的錯誤分別處理，從而避開復雜的檢測和錯誤檢查邏輯。在C#中.C# 4.0實現了動態成員查詢實現了鴨子型別化。注意下例中類方法InTheForest的引數型別被宣告為dynamic。using System;namespace DuckTypingpublic class Duckpublic class Personinternal class Programprivate static void InTheForest(dynamic duck)duck.Quack();duck.Feathers();private static void Game()Duck donald = new Duck();Person john = new Person();InTheForest(donald);InTheForest(john);private static void Main()Game();在Objective-C中.Objective-C，C和Smalltalk的一個交錯，像Smalltalk一樣，允許使用者宣告一個物件的型別為“id”並向它傳送任何資訊。傳送者可以測試一個物件以瞭解它能不能對一個訊息響應，物件可以在收到訊息的時候決定響應與否，如果傳送者傳送了一個接收者不能響應的訊息，一個異常會被丟擲。因此，鴨子型別在Objective-C中被完全支援。在ColdFusion中.web應用程式指令碼語言ColdFusion允許函式引數被指定為型別為"any"。對於這種引數，任意物件都可被傳入，函式呼叫在執行時被動態繫結。如果物件沒有實現一個被呼叫的函式，一個可被捕獲並優雅地處理的執行時異常將被丟擲。在ColdFusion 8中，這也可以被一個已定義的事件onMissingMethod()而不是異常處理器處理。另一個可替代的引數型別"WEB-INF.cftags.component"限制傳入引數是一個ColdFusion元件，在一個不正確的物件傳入時它提供了更好的錯誤訊息。