我對面向對象編程的一些理解
面向對象的核心特性包括封裝、繼承和多態。
封裝
封裝指的是把數據和與這些數據相關的行為進行綁定,並對外提供訪問介面。在 C++ 和 Java 等語言有 private 關鍵字,可以把成員變數或成員函數隱藏起來,不允許使用者從外部進行訪問。封裝是一種實現「信息隱藏」的手段,它可以把數據的組織方式和行為的具體實現隱藏起來,使得外部調用者無需依賴行為的具體實現方式。例如隊列類 queue ,在使用時,我們不需要知道數據是用數組儲存的還是用鏈表儲存的,也不需要知道 push_back 的具體操作是怎麼做的,這就實現了行為與介面的解耦。封裝還有助於進行模塊化編程,只要介面定義良好,不同的類可以由不同的程序員維護,測試也可以單獨進行。為了達到行為與介面的完全解耦,部分 OOP 語言引入了 private 等關鍵字,這樣可以禁止調用者訪問除了介面以外的數據和方法。
繼承和多態
我理解的繼承有兩種。
一種是對現有類進行擴展,子類可以復用父類的代碼,可以重寫部分代碼,增加一些成員,提供更多介面等。在這種繼承的語境下,子類和父類應該視為同一級別的類,他們的關係就像普通汽車和裝了車載音響的汽車。裝了車載音響的汽車雖然繼承自普通汽車,但兩者並沒有邏輯上的父子關係,他們對外提供的服務都是類似的。
第二種繼承是介面繼承,指的是實現了某個介面的類,即可被視為繼承自該類。這種繼承和多態有千絲萬縷的聯繫,我們沒法孤立地看待它們。在動態語言如 Python 中,這種介面繼承又叫鴨子類型,意為「只要它會像鴨子一樣叫,我就把它看作鴨子」。而在 Java 里,我們通常不說「繼承」一個介面,而說「實現」一個介面,這是因為子類並沒有從介面的定義中「繼承」到任何代碼。從集合論的角度看,如果把實例化的對象看作集合中的元素,那麼類和介面都是對象的集合。一個類 A 如果實現了介面 I ,那麼集合 A 就是集合 I 的子集。實際上,集合 I 是所有實現了介面 I 的類對應集合的並集。其他繼承模式如 mixin 模式和 trait 模式都和介面繼承模式類似,但又有細微的差別, mixin 強調代碼的復用,把小的功能組件插入其他類,讓其他類也具有這樣的功能,典型的應用場景是對日誌系統的支持, 一個日誌類通過 mixin 的方式混入其他需要日誌功能的類,使得它們也具有記錄日誌的功能。 Trait 和介面(Interface) 類似,可以自帶一些默認實現,和 C++ 的 abstract class 在語法層面上差別不大。但如果你只是把它理解為 abstract class ,那麼你有極大的可能會濫用它。還是應該從子類型的角度去理解和應用 Trait 。
在面向對象編程中,還有一些設計原則。
- 開閉原則:對擴展開放,對修改封閉
- 里式替換原則:程序中的所有用基類的地方,都可以用子類代替
- 依賴倒轉原則:依賴於抽象而不依賴於具體
- 介面隔離原則:將大介面分散成小介面
- 單一指責原則:一個類的功能盡量單一
- 最小知識原則:一個對象應該盡量少的了解其他對象
用上文提到的汽車和帶音響的汽車作為例子,開閉原則要求你給汽車加音響的時候,不能對原來的車進行大的改動;里式替換原則要求任何可以用汽車地方,你的帶音響的車都可以開過去替代;依賴倒轉原則指的是你當需要車作為交通工具的時候,應該依賴的是一個可以載人的工具,而不是某一輛沒有音響的內燃機汽車;介面隔離原則指的是當你的汽車既可以是載人工具,也可以是一坨可回收的物品時,你不應該定義一個介面為可回收的載人工具,而應該把它們拆開;單一職責原則和最小知識原則比較簡單,不再贅述。
了解面向對象編程思想的基本內容之後,讓我們回到程序設計本身。我們引入面向對象思想到底是要解決什麼問題?或者說,我們的需求是什麼?
一個需求是復用,包括代碼的復用和思維的復用。代碼的復用可以降低開發和維護的難度,增強代碼可讀性。思維的復用指的是你可以很輕鬆地復用你以前的經驗,例如你對某個介面很熟悉,所以你對這個介面的所有經驗就能輕鬆地應用到這個介面的所有實例上。很多時候我們說一個語言或框架優雅,大都是因為它的架構、設計、行為、命名等都具有一致性,可以讓你輕鬆地復用你的思維,這使得你只需要記憶很少的東西,便可以玩轉一個複雜的框架。
另一個需求是合作,大型項目是不可能一個人寫完的,那麼怎樣才能把大家的工作組合在一起呢,這就需要模塊化編程了。在面向對象中,一個類就是可以是一個模塊,不同的類可以由不同的程序員來完成。
當然還有一個需求是解耦,這個需求既是為復用的需求服務,也是為合作的需求服務。面向對象中的封裝可以實現行為和介面的解耦,但這只是解耦的一種形式,在其他編程思想中有其他的解耦方式,例如函數式編程就並不強調介面和行為的解耦,它強調數據和行為的解耦,而且由於高階函數的大量應用,你可以用簡單的函數組合出複雜的行為,所以行為不需要也不應該和數據綁定在一起。
總結一下,面向對象思想只是一種方法,封裝、繼承和多態也並不是程序設計中要追求的終極目標,我們根本的需求是復用、合作和解耦,為了面向對象而濫用面向對象,反而會影響復用、合作和解耦,這一點是需要我們牢記在心的。
推薦閱讀: