隱式介面實作

2006 年 1 月 4 日

Java 和 C# 都採用相同的純介面類型模型。您可以透過 interface Mailable 宣告一個純介面,然後您可以宣告您使用 class Customer implements Mailable (在 Java 中) 來實作它。一個類別可以實作任意數量的純介面。此模型忽略的一件事是,每當您有一個類別時,您都有隱式介面。

Customer 的公開隱式介面是 Customer 上宣告的所有公開成員。(此隱式介面存在於我所見過的每個 OO 語言中。)Java 和 C# 都不允許您做的一件事是實作隱式介面 - 也就是說您無法撰寫 class ValuedCustomer implements Customer

實作隱式介面是什麼意思?基本上,它會告訴類型系統,ValuedCustomer 類別實作 Customer 的公開介面中宣告的所有方法,但不會採用任何實作,也就是它的公開方法主體和非公開方法或資料。換句話說,我們有介面繼承,但沒有實作繼承。

這等同於將 Customer 變更為包含 customer 所有公開方法的介面,然後有一個 CustomerImpl 類別來實作此介面。

為什麼這可能會很有用?我記得過去的一個案例是在 Java 的早期,在目前的集合架構之前。我們想要用我們自己的實作取代 Vector 類別,但無法做到,因為 Vector 是個類別,我們只能對其進行子類別化。有時您會遇到像這樣的案例,當函式庫不提供介面以允許自由替換時,沒有此功能,我們就卡住了。

這在測試中尤其常見。有許多時候您想要建立存根,但除非您有介面,否則很困難或不可能。它也會導致定義純介面類型,而這樣做的唯一原因是支援測試替換。雖然使用 InterfaceImplementationPair 是一種常見的方法,但我們許多人並不贊成。隱式介面實作將是一種更乾淨的方法。

那麼為什麼語言不允許這樣做?我並不真正知道 - 但我不是語言設計師。我曾經有機會詢問 Anders Heljsberg 這個問題,他的回答與他偏好僅在您明確宣告成員為虛擬時才覆寫的觀點非常相似。基本上,這是對子類別 (或在此情況下為實作者) 破壞超類別的擔憂,這涉及如何使用子類別化的更廣泛主題。然而,這只是一次簡短的晚餐對話,所以我並不確定我們是否真的深入討論了這個問題。

寫完這篇文章後,我以前的同事 Mike Rettig 指出,這篇文章的問題之一在於類別實際上會定義多個隱含介面。例如,在 Java 中,customer 類別實際上會定義四個隱含介面:public、protected、package 和 private。如果物件與 Customer 合作,它可能會使用這些介面中的任何一個(另一個 customer 實體可以使用 private 功能)。如果我們要實作隱含介面,我們必須實作所有內容,或定義我們的執行範圍。我不知道類型系統要如何追蹤這一點有多困難。

當然,我給出的範例大多只適用於 public 隱含介面,因此在實務上這可能就夠了。

Ian Griffiths 指出,這個問題可能是關於混合類別和介面。Microsoft 的 COM 技術實際上將兩者嚴格區分開來:「如果你要在 COM 中使用物件,你必須透過介面來執行。因此,你隨時都可以建立自己的實作。」這在 VB 6 中變得相當透明,因為 COM 介面可以在幕後產生。

這個問題不會出現在動態類型語言中。如果你要實作另一個類別的介面,你只需要實作相同的方法,並在需要的地方使用物件即可。你不需要實作每個方法,只要實作在特定互動中使用的那些方法即可,而這是你關注的重點。這是一個非常適合測試的架構 - Smalltalker 一段時間以來稱之為 Imposter 模式。在 Java 中使用動態代理來執行這類事情也很常見,儘管我覺得隱含介面實作會更具溝通性。

這些內容有任何重要性嗎?它似乎主要是測試的問題,如果你無法使用重新實作,就更難插入TestDouble - 如果超類別需要與資料庫建立真實連線,則經常進行子類別化無法解決問題。它很可能只是一個測試問題 - Robert Conley 告訴我,他經常使用 VB6 的重新實作功能來進行測試,但從未發現生產程式碼需要這樣做。