見山只是山 見水只是水——提升對繼承的認識
溫 昱
封裝、繼承、多態是OO的三大特性,由此可見繼承思想的重要性。但是,不少人對繼承的理解過多地局限在OOP層面,從而限制了繼承思想在OOD層面的巨大作用。筆者認為,軟體工程師應該不斷提升對OO思想的認識層面,加強實際開發能力。
本文站在OOD的角度,將繼承看成實現OOD的強大手段,通過具體例子,說明針對介面編程(Program To An Interface)、混入類(Mix In Class)、基於角色的設計(Role-based Design)這三個與繼承緊密相關的著名OOD技巧。
一、從一則禪師語錄說起
《五燈會元》卷十七中,有一則青原惟信禪師的語錄:“老僧三十年前未參禪時,見山是山,見水是水。及至後來親見知識,有個入處,見山不是山,見水不是水。而今得個休歇處,依前見山只是山,見水只是水。”
禪師高論,頗具哲理,講的是悟道的過程。其實,領悟OOD之道的過程又何嘗不是如此呢。
1、見繼承是繼承——程式員境界
初學OOP的人,大多處在“見繼承是繼承”的層面,最關心的是類的文法、類的成員變數、類的成員函數等這些實現層的東西。這是程式員境界。
2、見繼承不是繼承——成長境界
開始研習OOD之時,又往往跳到另一個極端,只關心設計,而無心(也可能是無力)關心實現,處在所謂“見繼承不是繼承”的層面。在這個階段的人,腦中的興奮點是“設計”,是職責分配、介面設計、可重用性、可擴充性、耦合度、彙總度等這些設計層的概念。這是成長境界。
3、見繼承只是繼承——設計師境界
學通OOD之後,會達到“見繼承只是繼承”的層面。一個“只”字,體現了繼承背後的“設計理念”才是該境界的要害。但是,這個階段和第二階段不同,第二階段是一味的否定,而本階段是否定之否定,把OOP層面的繼承機制看成用來實現特定OOD的手段加以利用。這是設計師境界。
二、從OOD層面認識繼承
在OOP層面,除了類、成員變數、成員函數這些最基本的概念,最重要的就是代碼重用和名字空間的可見度了。而OOD層面,最基本的概念是類、職責、狀態、角色這些更抽象一級的概念,及其相關的耦合度、彙總度、可重用性、可擴充性、可維護性等。可見,雖然OOD最終要依賴OOP作為實現手段,但顯然OOD和OOP並非在同一抽象級上,有不同的概念體系和思維方式。
再說繼承。單純從OOP層面看,繼承是一個通過複用父類功能而擴充應用功能的基本機制,它允許你根據舊的類快速定義新的類;還有些人用繼承僅為了擷取名字空間的可訪問性。但是,從OOD層面看,繼承可以演變出 “Is-A”、“Plays Role Of”等抽象的設計概念。因此,擔任設計師角色的人如果自己還限制在OOP的層面,“設計乏術”的局面是不可避免的。總之,提升對繼承的認識,對活用介面繼承和實現繼承這兩種繼承機制來實現OOD意圖非常重要。
與繼承相關的OOD技巧有很多,本文僅討論比針對介面編程、混入類、基於角色的設計這三種技巧,下圖展示了它們和繼承的關係。
三、針對介面編程——隔離變化
1、相關理論
耦合是依賴的同義字,被定義為“兩個元素之間的一種關係,其中一個元素變化,導致另一個元素變化”。抽象耦合被定義為“若類A維護一個指向抽象類別B的引用,則稱類A抽象耦合於B”。
依賴性倒置原則(Dependency Inversion Principle)形式化了抽象耦合的概念,明確表述了應該“依賴於抽象類別,不要依賴於具體類”。
針對介面編程遵守上述原則,從而在很大程度上阻止了變化波及範圍的擴大,有效地隔離了變化,有助於增強系統的可重用性和可擴充性。
2、針對介面編程舉例——用於體繫結構設計
根據經典的Coad的OOD理論,一個項目通常包含四個層:使用者介面層、問題領域層、資料管理層、系統互動層,如下圖所示。
將體繫結構劃分為層的一個很大好處是,這些層形成了開發小組的自然分界——每層的開發人員所需要的技巧是不同的。使用者介面層的開發小組需要瞭解將使用的使用者介面工具包;資料管理層的開發小組需要熟悉相關的資料庫、持久工具或者使用的檔案系統;系統互動層的開發小組需要瞭解通訊協議和用到的中介軟體產品;問題領域層的開發小組不需要瞭解這些知識,他們需要最深的領域知識,以及用到的相關分布對象或組件技術。
但是,要真正使得各個開發小組最大限度地獨立開發,還需要一個穩定的體繫結構設計做保證才行,其設計的核心思想是:問題領域層“不依賴於”其他任何層,而其他任何層“只依賴於”問題領域層。如下圖所示。
該體繫結構設計的實現,極為重要的一點,就是要使用針對介面編程的技巧。以系統互動層對問題領域層的單向依賴為例:
Ø 如果系統互動層要調用問題領域層的操作,直接調用即可。
Ø 如果問題領域層要調用系統互動層的操作,需要由問題領域小組定義一個通用的抽象介面,通過針對介面編程調用這個抽象介面;而系統互動小組通過介面繼承機制,定義抽象介面的子類,該子類完成抽象介面的具體實現。
筆者曾有一個項目,該系統需要即時地將本系統的資料變化,通知遠端的另一個系統。相關設計如下圖所示。在問題領域層,僅包含了一個抽象介面CChangeReporter,而並不關心CChangeReporter的具體實現。系統互動層擁有選擇具體實現方法的自由,比如CSoapChangeReporter是用SOAP通訊協議實現的CChangeReporter, CTcpChangeReporter是用TCP協議實現的CChangeReporter。而且假設由於技術的或商業的原因,將來需要同時支援多種通訊協議,也比較容易。
3、針對介面編程舉例——用於類設計
筆者曾在《運用設計模式設計MIME編碼類別》一文中,詳述了如何使用原則模式來設計一個可重用、易擴充的MIME類層次,其中抽象介面類CMimeAlgo起到了至關重要的作用,現簡述如下。
使用者通過CMimeString使用MIME編碼的功能,CMimeString允許使用者在運行過程中動態配置MIME編碼的具體演算法;具體MIME編碼演算法由CMimeAlgo類層次提供,具體的CMimeAlgo子類的執行個體化是由CMimeString根據使用者的配置動態完成的;要增加新的MIME編碼演算法,只需實現新的CMimeAlgo子類,並簡單擴充CMimeString的動態執行個體化代碼即可。如下圖所示。
四、混入類——更好的重用性
1、相關理論
混入類被定義為“一種被設計為通過繼承與其他類結合的類”,它給其他類提供可選擇的介面或功能。
從實現上講,混入類要求多繼承;混入類通常是抽象類別,不能執行個體化。
混入類的作用在於:它不僅可以提高功能的重用性,減小代碼冗餘;而且還可以使相關的“行為”集中在一個類中,而不是分布到多個類中,避免了所謂的“代碼分散”和“代碼交織”問題,提高了可維護性。
2、混入類舉例
來看一個具體項目。在一個信用卡客戶服務系統項目中,要求能夠以多種方式發送多種資訊給使用者,並能夠適應未來業務的發展變化。
當前系統需要支援的發送方式:
Ø 列印(並郵寄)
Ø Email
Ø 傳真
可預見的未來要支援的發送方式:
Ø 手機簡訊
Ø PDA訊息
當前系統需要支援的待發送資訊:
Ø 信用卡對賬單
Ø 信用卡透支催收單
可預見的未來要支援的待發送資訊:
Ø 信用卡新業務宣傳單
Ø 信用卡促銷活動宣傳單
下面是一些設計考慮。一種發送方式要支援多種待發送資訊,我們希望發送功能有很好的可重用性;為了方便未來加入對新的發送方式和發送資訊的支援,設計必須具有良好的可擴充性。相關設計如下圖所示。其中採用了混入類的OOD技巧,用一個CSendableDoc作為混入類,支援發送功能的重用;CSendalbeDoc還採用了策略模式支援發送方式的擴充。
五、基於角色的設計——使用角色群組裝協作
1、相關理論
協作被定義為“多個對象為了完成某種目標而進行的互動”。角色被定義為“特定協作中的對象的抽象”,它“僅定義了對象特徵的一個對某協作有意義的子集”。協作和角色的概念和現實世界很接近,比如下圖中,Jane教授扮演三個角色——母親、妻子、教授。
介面分離原則(Interface Separation Principle)信奉“多個專用介面優於一個單一的通用介面”的思想,因為“任何介面都應當具有高內聚性”,以便“保證實現該介面的類的執行個體對象可以只呈現為單一的角色”。
基於角色的設計的意義在於:我們很容易通過已有角色的組合來構造新的協作,以完成新的功能。而且,從UML類圖可以很自然地匯出基於角色的設計方案,例如:
從上面的類圖很自然地匯出下面的設計:
2、基於角色的設計舉例
比如,待開發的一個系統,其後台資料來源可能是關聯式資料庫、一般的檔案、還可能是另一個私人資料庫。既然介面可以隔離變化,我們可以定義一個單一的介面,為所有的資料客戶類提供服務。如下圖所示。
但是,上面的設計違背了基於角色的設計思想,根本不能保證“實現該介面的類的執行個體對象可以只呈現為單一的角色”,這會帶來一些問題。比如,有一個資料客戶類,不需要插入、更新等功能,而僅僅需要對資料進行讀操作,這時顯然一個提供“讀”服務的“角色”是最合理的設計,但CRowSetManager卻是如此之“寬”的一個介面。最終,我們可以這樣來改進設計,如下圖所示。
參考文獻:
《設計模式》 Erich Gamma等著 李英軍等譯
《重構——改善既有代碼的設計(影印版)》 Martin Fowler
《UML物件導向設計基礎》 Meilir Page-Jones著 包曉露等譯
《Java設計:對象、UML和過程》 Kirk Knoernschild著 羅英偉 汪小林譯
《特徵驅動開發方法原理與實踐》Stephen R. Palmer, John M. Felsing著 熊煥宇等譯
《Object-oriented programming: Role-based design》 W.McUmber 來自網上的投影片
《Role = Interface: A Merger of Concepts》 Friedrich Steimann 來自JOOP
《運用設計模式設計MIME編碼類別》 溫昱 《CSDN開發高手》第1期
以及來自www.objectmentor.com的多篇文章
作者簡介:
溫昱,架構設計師,資深諮詢顧問,松耦合空間(http://lcspace.nease.net)創辦人。擅長物件導向、架構和架構設計,對設計模式、UML和軟體工程有深入研究。可以通過wenyu@china.com和作者聯絡。