Skip to main content

[探索 3 分鐘] 書本提到的 class, interface, abstract

類別、介面、抽象這些詞, 應該算是物件導向的基礎知識, 但由於他有一點抽象, 很多人還是覺得跟他們有些距離, 解釋也不一定到位。面試時大家又很愛問, 似乎想要知道你對物件導向的認知到哪邊。小弟有幸在 10 年前參加公司的讀書會討論這兩本書 :《物件導向設計模式 - 可再利用物件導向軟體之要素》與《Effective C++ 3/e 中文版 - 改善程式與設計的 55 個具體作法》看完之後雖然還是沒有具體答案, 但肯定直到現在都有一個「物件導向感覺」的。

以下請謙虛參拜。


Object 物件

《物件導向設計模式》

物件導向程式是由物件所構成。物件將資料和可施於其上的程序包裝在一起, 此處的程序通常較作 method 或操作 (operation)。當物件收到客戶端 (client) 送來的要求 (request), 或稱為訊息 (message) 時, 就會執行對應的操作。

訊息是唯一可令物件執行操作的管道, 操作則是唯一可改變物件內部狀態的管道。有了這些限制, 物件內部狀態可說是被妥為封裝了起來 (encapsulation) : 無法接觸及, 無法自外界窺伺。

Interface 介面

《物件導向設計模式》

介面是物件導向系統的基石。
物件所宣告的每一個操作, 都得訂出該操作的名字、參數、回傳值, 也就是該操作的外貌簽名 (Signature)。所有已訂定的物件操作之 Signature, 稱為此物件的介面  (Interface)。
物件的介面, 界定出能送給此物件的訊息類型; 只有符合物件介面的 signature, 才能送給此物件。想辨識物件, 只有靠介面這條路; 若不知道介面, 便無從得知物件的資訊, 也無法叫他做任何事。物件介面並不含任何時做事項 — 物件有權自主決定該如何時做這些操作。
兩物件即使介面完全一樣, 也可能會有完全不同的實作。

 《Effective C++》

對於其他語言轉換至 C++ 陣營的程式員而言, 另一個可能造成困惑的術語是介面 (interface)。  Java 和 .NET 語言都提供 interfaces 為語言元素, 但 C++ 沒有。當使用術語「介面」時, C++ 通常談的是函式的簽名 (signature) 或 class 的可存取元素 (如 class 的「public / protected / private 介面」), 或是對某 template 型別參數須為有效的一個算式。這裡的介面完全是指一般性的設計觀念。

Polymorphism 多型

《物件導向設計模式》

當物件收到訊息要求時, 會同時根據訊息和物件來決定該啟動哪一個操作。實作方式不一樣的兩物件可能都會支援同一訊息要求, 所以在執行期決定該將此一要求導引到哪個物件的哪個操作的機制, 就叫做動態繫結 (dynamic binding)。動態繫結意味著 : 你所送出的訊息, 直到執行期才會知道送到哪一個實作體上, 如此一來, 你就能針對特定介面來寫程式, 讓符合特定介面的物件來接收訊息。
動態繫結 (Dynamic Binding) 更讓你能夠在執行期以同一介面的多個物件交互替換, 這種替換性質就叫做多型 (Polymorphism)。
有了多型, 客戶端就不必對引用物件作過多假設, 只需支援特定介面; 亦可簡化客戶端的定義、拆解物件、在執行期改變彼此關係。

 《Effective C++》

物件導向編程世界總是以顯式介面 (explicit interfaces) 和執行期多型 (runtime polymorphism) 解決問題。對 classes 而言介面是顯式的 (explicit), 以函式簽名為中心。多型則是透過 Virtual 函式發生於執行期。
注意 templates 則是相反的概念,  強調編譯期多型 (compile-time polymorphism) 與隱式介面 (implicit interface) - 不奠基於函式簽名式, 而是由有效算式 (valid expressions) 組成, 多型則是透過 template 具現化和函式重載決議 (function overloading resolution) 發生於編譯期。

Design Pattern 設計模式

《物件導向設計模式》

設計模式協助你辨認出介面的關鍵要素和交互傳遞的資料類型, 也可能教你不要把某些東西擺到介面裡。
設計模式也制定介面之間的關係, 尤其是會要求某些類別必須具有類似的介面, 或者限制某些類別所該具備的介面。譬如說,  decorator 和 proxy 模式都要求 decorator 和 proxy 物件必須和他們的對象有相同的介面。visitor 則要求他的介面必須能反映所有對象的類別。

Class 類別

《物件導向設計模式》

物件的具體實作細節是由類別 (Class) 所定義的, 類別定義出物件的內部資料和布局、物件所能夠執行的操作。
類別可具現出 (instantiate) 物件, 我們稱此物件是類別所生之個體 (instance)。具現程序會替物件內部資料及搭配的操作配置出空間, 同一類別可具現出許多相似的物件個體。

 《Effective C++》

C++ 就像其他 OOP (物件導向編程) 語言一樣, 當你定義一個新的 class, 也就定義了一個新 type。身為 C++ 程式員, 你的許多時間主要是用來擴張你的型別系統 (type system), 而如何設計高效的 classes ? 請思考
  • 新 type 的物件應該如何被產生和銷毀 ?
  • 物件的初始化和物件的賦值應該有什麼樣的差別 ?
  • 新 type 的物件如果被 passed by value 意味著什麼 ?
  • 什麼是新 type 的「合法值」
  • 你的新 type 需要配合某個繼承圖系 (inheritance graph) 嗎 ?
  • 誰應該取用新 type 的成員 ?
  • 你的新 type 有多麼一般化 ?
  • ...

Abstract 抽象

《物件導向設計模式》

抽象類別 (Abstract Class) 主要目的是替多個子類別訂出共通介面。
抽象類別會將部分或全部的實作細節交付給予子類別的操作去實現, 因此抽象類別無法具限出物件個體。抽象類別有宣告、但尚未實作的操作, 叫做抽象操作 (abstract operation); 與抽象類別相反的則叫做具象類別 (concrete class)。

 《Effective C++》

C++ 的抽象機制是用 virtual 這個關鍵字。virtual 函式的目的是允許衍伸類別 (derived class) 的實作得以客製化, 它在不同的 derived classes 中有不同的實作碼 (virtual 可以有預設實作)。
如果 class 不含 virtual 函式, 通常表示他並不意圖被用做一個 base class。

Inheritance 繼承

《物件導向設計模式》

請留心分辨類別繼承和介面繼承 (或稱子型別性質, subtyping) 之間的差異。類別繼承是藉某一物件來定義另一物件的實作細節, 是一種共享程式碼與內部布局的機制; 相對的, 介面繼承 (子型別性質) 則在描述物件之間的可替換性。

但這兩個觀念很容易混淆, 因為許多程式語言根本就沒有區分。像 C++ 和 Eiffel 語言裡, 「繼承」同時代表「介面繼承」和「實作繼承」兩種意思。Smalltalk 裡, 「繼承」只代表「實作繼承」一種意思。

 《Effective C++》

C++ 的 OOP (物件導向編程) 有可能和你原本習慣的 OOP 稍有不同。「繼承」可以是單一繼承或多重繼承, 每一個繼承連結 (Link) 可以是 public, protected 或 private, 也可以是 virtual 或 non-virtual。當你使用某個特定構建你真正想要表達的意思 ? 例如「public繼承」意味 "is-a", 如果你嘗試讓他帶著其他意義, 你會惹禍上身。同樣道理, virtual 函式意味「介面必須被繼承 」, non-virtual 函式意味「介面和時作都必須被繼承」。
is-a (是一種) 並非是唯一存在於 classes 之間的關係, 另外兩個常見的關係是 has-a (有一個) 和 is-implemented-in-terms-of (根據某物實作出)。

名言佳句

針對介面而寫, 不要針對實作
Program to an interface, not an implementation.

多用物件複合技術, 少用類別繼承
Favor object composition over class inhertiance.

讓介面容易被正確使用, 不易被誤用
Make interfaces easy to use correctly and hard to use incorrectly.

參考資料

  • https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%8F%AF%E5%A4%8D%E7%94%A8%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%BD%AF%E4%BB%B6%E7%9A%84%E5%9F%BA%E7%A1%80

Comments