2009年6月30日 星期二

物件繼承的物件觀點

「欣郁是個人。」
「欣郁生了孩子。」
欣郁生了個孩子,表示欣郁是女人,會生孩子很正常。即使這兩句話並沒有提到欣郁是個女人,正常人也可以藉由常理推敲出最有可能的結論。但是,如果不加入過去的經驗,純粹從以上兩句話來推敲,則會得到「人會生孩子」這種以偏概全的結論。正常人會從常識中選擇合理的解釋,但對沒有常識的程式語言編譯器而言,這種結論是上述兩句話的正解。這就是自然語言與程式語言之間的鴻溝:有些東西你認為是常識,編譯器卻不懂你的意思。
這篇文章要談的是,你如何正確地將上述兩句話依照物件繼承的類別觀點一文中的類別圖規格轉換成Java語言。
如果我們直接將上述兩句話逐句翻譯成Java程式碼:

「欣郁是個人。」 → 人類 欣郁 = new 人類();
「欣郁生了孩子。」 →
人類 孩子 = 欣郁.生產();

這兩行程式碼大有問題!
首先,我們並沒有說清楚欣郁究竟是男人還是女人。如果欣郁是男人,那麼欣郁.生產()就不合理。因此我們必須把程式碼改為:

人類 欣郁 = new 女人();
人類 孩子 = 欣郁.生產();

這段程式碼看起來較合理,起碼我們已經說明了欣郁是個女人的事實。然而,這樣的程式碼仍然無法編譯成功!
大家可能認為現在欣郁「事實上」是個女人,因此欣郁.生產()理應合法,那為什麼仍然無法編譯通過?理由是,當你把「欣郁」變數宣告成「人類」型態時,我們就把欣郁的「女人」的特性忽略掉了。這就好像我們說「人類是一種動物,所以人類會覓食」時,是把人類一般化成動物,而忽略掉人類獨有的特性(例如交談)。反之,我們不會說「人類是一種動物,所以人類會交談」因為這句話代表「動物會交談,而人類是一種動物,所以人類會交談」這樣的邏輯當然是錯誤的。同樣的道理,「女人會生產」並不代表人類都會生產。現在如果把欣郁一般化成人類看待,則我們就不能去看她獨有的「生產」功能,只能看到人類共有的「交談」功能。人類的定義裡並沒有生產的功能,而欣郁是人類,欣郁.生產()當然不合理。要讓這段程式碼能夠成功編譯,我們必須把欣郁當成女人看待,因此修改成:

女人 欣郁 = new 女人();
人類 孩子 = 欣郁.生產();

這樣的程式碼就合法了。
要理解上面的解釋或許很費腦筋,畢竟不是每個人的邏輯都相當清楚,幸好我們可以使用物件繼承的類別觀點一文中的功能觀點來清楚解釋每一個步驟。

new 人類();

可以用下圖表示:

※表示物件的功能觀點圖為了與表示類別的功能觀點圖作區分,使用較具立體感的顏色來表達「照類別規格產生的實體物件」的效果。
※本文將不會去區別參照與指標,因「指標」講起來比較傳神。
人類 欣郁 = new 人類();

則可以表達為「欣郁指標」指向「人類物件」:

現在就可以清楚看到欣郁.生產()是不合法的,因為圖中的人類物件根本沒有「生產」功能。

那麼,如果是這行程式碼,該怎麼解釋呢?

人類 欣郁 = new 女人();

如下圖:



※在此不管JVM內是否這麼實作,先從邏輯的角度剖析。

new 女人()產生的雖是女人物件,人類型態的欣郁指標卻是指向女人物件中屬於人類的部分,欣郁.生產()也就不合法,因為指標所指向的物件仍然不具有「生產」功能。
根據上述畫圖的原則,很自然可以推出下面這行程式碼代表的意義:

女人 欣郁 = new 女人();



由於欣郁指標現在是女人型態,指向女人物件的整個區塊,欣郁.生產()就變得合法。
同樣的道理也能套用在物件的轉型上,將在下篇文章說明。

沒有留言:

張貼留言