面對對象中的繼承 優點如下: 缺點如下: 里氏替換原則的定義 如果對每一個類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程式P在所有的對象o1都代換成o2時,程式P的行為沒有發生變化,那麼類型S是類型T的子類型. 通俗點講,只要父類能出現的地方子類就可以出現,而且替換成子類也不會產生 ...
面對對象中的繼承
優點如下:
- 代碼共用,減少創建類的工作量,每個子類都擁有父類的方法和屬性
- 提高代碼的重用性
- 子類可以形如父類,但又異於父類
- 提高代碼的可擴展性,很多開源框架的擴展介面都是通過繼承父類來實現的
- 提高產品或項目的開放性
缺點如下:
- 繼承是侵入性的.只要繼承,就必須擁有父類的所有屬性和方法
- 降低代碼的靈活性.子類必須擁有父類的屬性和方法,讓子類自由的世界中多了些約束
- 增強了耦合性.當父類的常量、變數和方法被修改時,需要考慮子類的修改,有時更會帶來非常糟糕的結果--大段代碼需要重構
里氏替換原則的定義
如果對每一個類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程式P在所有的對象o1都代換成o2時,程式P的行為沒有發生變化,那麼類型S是類型T的子類型.
通俗點講,只要父類能出現的地方子類就可以出現,而且替換成子類也不會產生任何錯誤或異常,使用者可能根本不需要知道是父類還是子類.但是反過來就不行了,有子類出現的地方,父類未必能適應.
里氏替換原則的規範(繼承的規範)
1.子類必須完全實現父類的方法
例如有一個打槍的游戲,類圖如下
其 Soldier 代碼如下
註意: 在類中調用其他類時,要使用父類或介面,否則就違背了里氏替換原則
這時,如果有一個玩具槍 ToyGun, 繼承自 AbstractGun,顯然是不合適的,因為玩具槍沒有傷害,可以新建一個抽象類 AbstractToy,將聲音、形狀都委托給 AbstractGun 處理,然後兩個基類下的子類自由延展,互不影響
註意: 如果子類不能完整的實現弗雷的方法,或者弗雷的某些方法在子類中已經發生"畸變",則建議斷開父子繼承關係,採用依賴、聚集、組合等關係替代
2.子類可以有自己的個性
里氏替換原則可以正著用,但不能反著用.在子類出現的地方,父類未必就可以勝任
3.覆蓋或實現父類的方法是輸入參數可以被放大
舉個例子,先定義一個 Father 類
然後再定義一個子類:
雖然子類方法與父類方法同名,但不是覆寫父類方法,而是重載,因為輸入參數不同
父類的參數範圍小,當調用子類 doSomething方法時,若參數為HashMap調用父類方法,若為Map調用子類方法
要是反過來,子類的 參數比父類範圍下,就違背了里氏替換原則
4.覆寫或實現父類的方法是輸出結果可以被縮小
這個也不難理解,也就是說父類方法返回的是Map時,子類可以返回HashMap
但是反過來,父類返回的是HashMap,而子類返回Map類型就違背了里氏替換原則
採用里氏替換原則的好處
增強程式的健壯性,版本升級是也可以保持非常好的相容性.即使增加子類,原有的子類還可以繼續運行.在實際項目中,每個子類對應不同的業務含義,使用父類作為參數,傳遞不同的子類完成不同的業務邏輯,完美!
採用里氏替換原則,應儘量避免子類的"個性",一旦子類有"個性",這個子類和父類之間的關係就很難調和了,把子類當作父類使用,將子類的"個性"抹殺;把子類單獨作為一個業務來使用,會讓代碼間的耦合關係變得撲朔迷離