摘要

  物件導向的繼承不是真實世界的繼承,要把心思放在物件的介面上,而不是物件的分類問題上。大家習以為常的將繼承比喻成父子關係,引導人們將心思放在分類問題上,這對寫程式實務上並沒有幫助。本文前段論述不恰當的比喻對寫程式造成的困擾,後段則說明如何將心思放在物件的介面上,快速掌握物件導向的精髓。


  這是去年寫的「物件導向系列」文章,只寫了一半筆就一直擱著,後來就沒興致寫了。一直放著也不是辦法,就把這未完成的作品給貼上來。

目錄

    • 繼承是物件導向三大特色之首
    • 通用的比喻:父子關係
    • 改良的比喻:生物演化
    • 重點在介面,而非分類

繼承是物件導向三大特色之首

  學習物件導向者,都知道物件導向語言有三大特色:繼承、封裝、多型。如果你也是這樣想,那麼就大錯特錯了!其實物件導向語言最大的特色是:物件,其次才是繼承、封裝、多型。

  無論是哪一本物件導向的書,都會花上很多的篇幅來介紹「繼承」,這是因為以電腦語言發展史的角度來看,「繼承」無論是在概念上,或是在實務上,都大大的提昇了程式語言的威力。在經過多年職場的洗禮之後,現在再回過頭來重新檢視物件導向的三大特色,我認為繼承其實為三大特色之末,繼承在實務上的好處不過就是reuse而已,並沒有其他特別令人激賞之處!(基於歷史情結,本文仍然推崇繼承為三大特色之首)。

  至於「封裝」就不得了了!「物件」毫無疑問的是物件導向技術的主體,而「封裝」則是影響物件導向程式好壞的評判標準。隨著本身累積的實務經驗越來越多,越覺得「封裝」才是物件導向技術的精髓,若以物件導向程式語言寫出完全不封裝的程式來,這支程式根本就是侮辱了物件導向技術,充其量不過是用了物件導向語法的傳統程序性程式而已。

  這裡的「多型」指的是「動態繫結」,它對待分屬不同類別的一群物件,以一致的介面來操作它們。這可視為另一種形式的封裝,將個別物件的差異隱藏起來,有助於提昇軟體的可維護性。


通用的比喻:父子關係

  在物件導向的世界,常可聽到以下的敘述:父類別所擁有的功能與特性,子類別在「繼承」的時候,直接就了擁有了相同的功能與特性,不需要做任何額外的事情。

  老子的東西就是兒子的,這對話乍聽之下似乎有道理,但越想越不對勁:不管是繼承財產,還是繼承債務,繼承應該是發生在死亡之後吧!

  事實上,父類別物件和子類別物件並不知道對方存在,它們的存在是看程式需要,和對方沒有任何關係。有時會有兩個父類別物件一個子類別物件,難道它們是同性戀家庭?有時根本沒有父類別物件卻仍然有子類別物件,這算無性生殖還是無中生有?父類別物件和子類別物件的存在性,是完全獨立的,父類別物件死了(解構)之後,它所擁有的任何財產(資料),沒有一項會繼承(轉移)給子類別物件。父類別物件和子類別物件之間並不是繼承的關係。

  若想得更仔細一點,可以發現:繼承講的不是物件和物件之間的關係,而是類別和類別之間的關係。物件講的是實體個體,有生(建構)也有死(解構),類別講的是依特徵分類,是概念上的東西。物件導向的繼承不是真實世界的繼承,而是「遺傳」:特徵的遺傳。中國人之所以黑髮黃皮,西洋人之所以金髮白皮,都是遺傳的關係,遺傳自他們各自的祖先。討論至此,終於瞭解物件導向前輩嘴巴講繼承,其實心裡想的是遺傳啊!呵呵!這是否叫做「口是心非」呢?

  咦!想得越仔細就會發現越多漏洞,總還是覺得哪邊怪怪的。孩子的特徵並不是完全遺傳自父親,還有一半的特徵是遺傳自母親啊!這~難道就叫做「雙重繼承」?即便是雙重繼承,也還是無法和現實情況對應上。大家應該都知道血型分成O型、A型、B型、AB型四種,血型A型的父母會生出血型A型的小孩,這聽起來很合理,但為什麼血型A型的父母會生出血型O型的小孩呢?這是由於父母各提供一個血型基因給小孩,A型、B型基因為顯性基因,O型基因則為隱性基因,當顯性基因遇到隱性基因,就只會表現出顯性基因的特徵,所以血型A型(A+O)的父母才會有可能生出血型O型的小孩。真實世界的「遺傳」,是由父母「各出一半」的特徵,但是物件導向世界的「雙重繼承」,卻是父母特徵的完全加總,不多不少。由此可見遺傳的比喻仍然是失敗的!


改良的比喻:生物演化

  想要有好一點的比喻,就要捨棄「父子」這些描述個體的詞彙,改而採用「人種」或「物種」這類描述特徵分類的詞彙。

  不管是父還是子,都是相同的人種,例如:中國人,所以才會擁有相同的特徵,例如:黑頭髮、黃皮膚。既然父與子的特徵相同,他們就屬於同一個類別(中國人),他們就不是父類別與子類別的關係了,呃!為了避免混亂,此後不再稱父類別與子類別,而是稱為基礎類別與衍生類別。

  那麼物件導向的繼承又該如何比喻呢?我的意思是說,基礎類別與衍生類別要如何比喻呢?這就要回到物件導向概念了,物件導向中的基礎類別擁有較少的特徵,衍生類別則擁有較多的特徵,衍生類別除了有基礎類別的特徵之外,還有一些基礎類別所沒有的特徵。而一個物件如果屬於衍生類別,它就擁有衍生類別的特徵,而且由於基礎類別的特徵它也都具備,所以它必定也屬於基礎類別。

  換句話說,一個物件如果屬於衍生類別,就必定也屬於基礎類別,甚至於我們會說:任何衍生類別物件亦是基礎類別物件。這就好比:我是人類,就一定是哺乳類,更是動物。因為人類就是許多哺乳類中的一種,而哺乳類是各式各樣動物中的一種。人類和獅子、大象同屬於哺乳類,一樣具有溫血、毛髮、雌性會哺乳等特徵,但亦有各自不同的特徵:人類可以直立走路,獅子會捕獵其他生物,大象具有長長的鼻子。

  物件導向中的繼承是一種特殊化,會比繼承之前多出一些特徵,通常是多一些東西,但偶爾多出來的東西是一種限制,這反而會讓它少一些東西。這些敘述有點像在講生物演化的過程,物件導向的繼承的確和演化有些微妙的對應關係,但過度著重於比喻,反而會讓自己迷失在字裡行間。讀者務必要記住一件現實:類別與繼承僅僅用於描述某個時間點的特徵分類方式,而不是要一字不差的找出真實世界的「族譜」。

  生物界的演化,可以從基因變異的軌跡,追本溯源逆推出族譜,Discovery頻道就曾經大費周章的找出全世界人類的族譜。至於寫程式,就比較看個人自由心證,不見得有公認的「基因」做基準。

  真實世界曾經有過一種生物,名為「始祖鳥」,由名字就可以知道牠是所有鳥類的始祖,始祖鳥同時具備爬蟲類與鳥類的特徵,牠是卡在中間的四不像。若要以物件導向語言,一字不差的描述真實世界的族譜,則始祖鳥要繼承爬蟲類,鳥類再來繼承始祖鳥。哇!完蛋了,那麼鳥類不就是一種爬蟲類,這和我們現有的知識是不合的!所以說,物件導向程式碼可以描述真實世界某個時間點的特徵分類方式,但是卻無法描述真實世界演化的所有過程。


重點在介面,而非分類

  我們知道「始祖鳥」是爬蟲類進化到鳥類過程中的過渡物種,但沒有人能夠拍著胸脯保證:始祖鳥是所有鳥類的共同祖先!頂多只能說:始祖鳥和鳥類有共同的祖先,而且始祖鳥比其他鳥類更「像」牠們共同的祖先。我們由基因的長相可以推論鳥類是由爬蟲類進化而來的,但是在現存物種中並沒有卡在中間進化到一半的生物,進化說僅僅只是推論而已。而始祖鳥化石的發現,彌補了這個理論與現實之間的「失落的環節」,成為進化說的證據,證明半鳥半爬蟲的物種曾經出現過。

  慢著,我們是在寫程式,不是在考古啊!瞭解「失落的環節」對寫程式而言並沒有幫助,我們只需要釐清一件事情,我的系統是把始祖鳥當成一隻鳥還是一隻爬蟲。如果是當成一隻鳥,就讓始祖鳥繼承鳥類,以鳥類的特徵為基礎,再把始祖鳥的爬蟲特徵加上去。如果是當成一隻爬蟲,那就讓始祖鳥繼承爬蟲類,以爬蟲類的特徵為基礎,再把始祖鳥的鳥特徵加上去。如果既把牠當成鳥又當成爬蟲,這時就要用到雙重繼承了。

  對寫程式而言,要把心思放在和物件互動的介面上,而不是物件的分類問題上。也就是說,始祖鳥是爬蟲還是鳥類是考古的問題,這並不重要,重要的是,我想要讓始祖鳥在天上飛,翱翔於天際,還是要讓始祖鳥在地上爬,穿梭於林間,或者更貪心一點,有時想讓牠飛,有時想讓牠爬。總之,我並不關心牠是鳥還是爬蟲,我只關心牠會不會飛、能不能爬。

  看完本篇,請再回頭看看第三集的六支程式,相信您會有更進一步的體悟與收穫!


更多「物件導向」文章:

  到底誰該去繼承誰? 物件導向初學者應該要知道的事情(三)

  為什麼我找出來的物件都是UI物件? 物件導向初學者應該要知道的事情(二)

  要如何找出物件呢? 物件導向初學者應該要知道的事情(一)

  [預告]: 物件導向初學者應該要知道的事情

更多「程式設計」文章:

  [分享] 程式設計寶庫,範例程式搜尋引擎

  最具殺傷力的小BUG

  最尷尬的『不恰當』,程式設計經驗談

  門面佈置的學問-UI開發

  寫程式會從哪邊下手? (問券調查)

  寫程式到底需不需要懂數學?

arrow
arrow

    牛奶 發表在 痞客邦 留言(1) 人氣()