1.基本介紹 裡斯科瓦(Barbara Liskov)使美國麻省理工學院電氣工程於電腦科學系資深教授,她是美國國家工程院院士,在程式語言、分散式計算、程式設計方法及軟體工程領域做出了卓越貢獻。裡斯科瓦於1987年提出了一個關於繼承的原則,也就是現在我們稱為的“里氏替換原則”。 里氏替換原則基於子類 ...
1.基本介紹
裡斯科瓦(Barbara Liskov)使美國麻省理工學院電氣工程於電腦科學系資深教授,她是美國國家工程院院士,在程式語言、分散式計算、程式設計方法及軟體工程領域做出了卓越貢獻。裡斯科瓦於1987年提出了一個關於繼承的原則,也就是現在我們稱為的“里氏替換原則”。
里氏替換原則基於子類對象可以賦給父類對象的“多態性”,表明子類可以替換父類,並且出現在父類能夠出現的任何地方。但是該原則要求繼承時需要遵守以下兩點:
- 繼承必須確保父類所擁有的“性質”在子類中仍然成立,且子類的程式執行無異常。
- 子類可以新增特有方法,並且可以重寫父類的抽象方法,但不能改變父類原有的方法。
這兩點也主要是衡量繼承的使用是否符合了里氏替換原則的標準。
2.繼承的濫用
通過一段小故事來講解,為了讓程式達到復用效果,濫用繼承且不遵守“里氏替換原則”從而使程式帶來意想不到的麻煩。
張三在學習了面向對象的技術後,深刻的感受到繼承復用帶來的快感,於是使用面向對象技術開發了一款“模擬鴨子游戲”,游戲中會出現各種鴨子,我們可以操作不同的鴨子進行游泳戲水或是呱呱叫。因為它採用了繼承的特性,設計了一個鴨子父類,並讓不同種類的鴨子都繼承此父類。
隨著游戲市場的競爭壓力加劇,張三為了讓自己的“模擬鴨子游戲”在市場上更受歡迎,張三開發擴展了一個新的功能,就是讓游戲中所有鴨子可以飛行。此時的張三已經被繼承的魅力沖昏了頭腦,他想到鴨子都有翅膀,只需要在Duck類中加上Fly()方法,基於繼承的特性所有鴨子都會復用這個Fly()方法,他心裡想:“妙啊,這可真是一招定乾坤啊”。
“模擬鴨子游戲”在加入飛行功能投入市場後,可怕的問題發生了。某個玩家將一隻“橡皮鴨”既然操作飛了起來,這顯然不符合一個程式的正常邏輯,因此張三受到了公司的嚴厲製裁,不僅游戲被下架還被罰款。這是怎麼回事?張三為了讓程式整體達到復用效果,忽略了極個別的鴨子其實並不具備飛行的能力,這使得某些並不適用該行為(飛行)的子類,也具有了該行為。
這個故事告訴了我們,為了讓程式達到復用的效果,繼承並不是一個“以逸待勞”的方案,使用繼承來達到復用必須要求繼承的成員在所有子類中均有統一性。張三之所以沒有使用好繼承,就是因為他使用的繼承過於片面,且沒有遵守里氏替換原則。然而,理解里氏替換原則的最終目的,就是促使我們更好的、規範的使用繼承,能夠明白什麼時候什麼情況下正確的使用繼承,以及其中蘊含的原理。
3.讓繼承性質合理化
接著張三開發“模擬鴨子游戲”的故事例子,我們來針對其中出現的問題:“即在使用繼承過程中,為父類加入新的行為,從而導致某些並不適合該行為的子類,也被迫繼承該行為。”來通過UML圖的方式,介紹兩種如何解決問題的方式。當然方式不止兩種,但我們最終目的是要讓我們的繼承符合“里氏替換原則”。
1.切斷不合理繼承,擴展新的父類。
設計思路:從Duck類中去除了Fly方法,以此切斷了橡皮鴨繼承Fly方法的途徑,然後新增“動物鴨”類包含Fly的方法,將會飛的鴨子繼承之該類,並且可以間接繼承Duck類中的共有特性(叫、游泳)。
2.組合方式
設計思路:該方式的做法和“繼承”不同的地方在於,“Fly方法”不是繼承來的,而是通過一個介面“組合”來的。子類可以根據自身情況實例化不同的“飛行介面實現類”來執行相應的飛行功能。該方式相對於第一種較為複雜,因為基本上已經形成了一個設計模式“策略模式”,在該章節對該模式不進行詳細介紹,目前只用知道有這種方式即可,後續在對於的專題會詳細展開。
知識改變命運