今天要談的是物件導向的幾個基本性質以及衍生出來的特性。
筆者相信,即便是寫了一年以上的程式,不懂物件導向的人也還是很多,光從學校的教科書來看,似乎也不是那麼容易了解,因為物件導向這件事是很抽象的東西,就像不懂畫的人在看畢卡索的畫一樣,只看到一堆顏色和線條,但根本看不出畢卡索要表達的是什麼。
物件導向的三個基本性質是:
物件將其本身的資料以及行為 (behaviors) 包裝在物件內部,外界除了透過物件所開放的成員 (ex: 屬性,方法,事件等) 使用物件外,不需知道物件內部的各種實作細節。
子物件透過繼承的方式,可以複製父物件所有開放 (透過存取修飾子設定) 的功能,外界在存取子物件時也可以得到父物件的所有開放的功能。而子物件本身也可以存取到父物件所開放的功能,或是進一步改變父物件的行為。
相同性質的物件並同一個成員的行為,會依物件不同而有所不同,這個性質經常出現在介面實作以及抽象類別的覆寫上,例如一個 Car 物件,定義有一個 getEngineProperties() 方法來取得引擎資訊,但 2000 年的車子和 2011 年的車子引擎資訊可能略有不同,而不同車子的引擎也未必相同,所以這些 Car 的子類別都會覆寫 Start() 方法,並且在用戶端存取不同的 Car 物件的 getEngineProperties() 方法時,會得到不同的結果。
(參考自維基百科 http://en.wikipedia.org/wiki/Object-oriented_programming)
而這幾年來物件導向界的 Design Pattern 愈來愈流行,所以又會加上下列幾個原則,其中有五項是著名的 SOLID 原則:
這是物件導向中最常用的,每個物件的成員原則上最好只負責一件事,不要給予太多的責任,否則在除錯上會有很大的問題,若需要整合時,再使用一個成員來整合這些單一職責的成員即可。這樣的原則有助於成員的單純,在測試與除錯上也會比較簡單快速。
又稱迪米特法則 (Law of Demeter),意指在使用物件前不需具備該物件實作所需要的知識即可使用,物件之間如果需要相互操作也僅需知道使用的成員即可。它還有一項原則是盡可能只與物件相關的其他物件溝通,而不向外部或與其無明顯關聯的物件作溝通。這樣的原則有助於單純化物件的使用方式與情境,減少因為不熟悉物件實作的背景知識而產生的風險。
意指對修改封閉,但對擴充開放 (software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification)。進一步的意涵是物件本身如果已經穩定後,就不應該對已經穩定的程式做異動,而是要另外使用不同的方法,以保護物件的用戶端不會因為成員的變更而可能造成不穩定的狀況,但物件是可以擴充的,因為擴充不會影響到物件現有的用戶端。
此原則最大的特色是介面的使用,適當的使用介面可以提升軟體的隔離性,以及日後的軟體組裝,因為介面本身是一個契約,不是物件的執行個體,而物件的呼叫可以利用介面來進行,以切斷與實體物件的相依性 (dependency),如此一來,就算是物件被更換 (ex: 版本升級),只要物件有實作與用戶端呼叫的相同介面,則用戶端即可不用重新建置就能使用新的物件。
用戶端通常在參照物件時,都是用實體物件參考的方式實作 (這是初學者或菜鳥時期練習累積的),但這樣會讓用戶端與物件間產生相依性,所以 DIP 原則要求的是用戶端參照的必須是介面而不是物件,如此,在介面的隔離性下相依性就會被切斷。最近很紅的 IoC 就是 DIP 的一種實現。
LSP 原則規範的是繼承的關係,也就是說在父子物件的關係中,子物件必須可以替換父物件。
這個原則是給不允許多重繼承的程式語言使用的,因為物件只允許單向繼承,所以最多只擁有一個父物件的功能,但這不利於軟體的重覆使用性,所以就有合成和聚合兩種作法,合成是一個 is-a 關係,多個物件被合成後,就是一個全新的物件,物件間的關係會很緊密,而聚合則是一個 has-a 關係,表示物件擁有某些物件的功能,但有可能是各自獨立運作,只是包裝在一個相同的物件。但不論是合成或聚合,對物件的重覆使用性都有直接幫助。
以上我們複習了學校教的物件導向性質以及最近流行的 OOP 原則,基本上中鳥階段必須要會這些東西,寫程式時才會有更多的思考元素,寫出來的程式也才會真的具備充份的物件導向與可高度重覆使用的 "好" 程式的特性。