在複習軟體構造課程的過程中,LSP原則,協變和逆變是課程後期的重點之一,鑒於其難度較高,特總結此篇博客以更好地學習這些知識。 ...
(防扒鏈接)
寫在前面
在複習軟體構造課程的過程中,LSP原則,協變和逆變是課程後期的重點之一,鑒於其難度較高,特總結此篇博客以更好地學習這些知識。
一、LSP原則
LSP原則,即Liskov Substitution Principle,常譯為里氏替換原則:
只要父類能出現的地方,子類就可以出現,並且替換為子類也不會產生任何錯誤或異常。
常用如下:
- 子類型可以增加方法,但不可刪
- 子類型需要實現抽象類型中的所有未實現方法
- 子類型中重寫的方法必須有相同或子類型的返回值或者符合協變的參數
- 子類型中重寫的方法必須使用同樣類型的參數或者符合逆變的參數。
- 子類型中重寫的方法不能拋出額外的異常,子類型也可以不拋出異常。異常必須滿足協變。
- Same or stronger invariants 更強的不變數
- Same or weaker preconditions 更弱的前置條件
- Same or stronger postconditions 更強的後置條件
在實際操作中需要註意以下三種情況:
(1)所有使用基類的地方必須能透明地使用子類替換,而程式的行為沒有任何變化(不會產生運行結果錯誤或異常)。只有這樣,父類才能被真正復用,而且子類也能夠在父類的基礎上增加新的行為。也只有這樣才能正確的實現多態。
(2)當一個類繼承了另一個類時,子類就擁有了父類中可以繼承下來的屬性和操作。但如果子類覆蓋了父類的某些方法,那麼原來使用父類的地方就可能會出現錯誤,因為錶面上看,它調用了父類的方法,但實際運行時卻調用了被子類覆蓋的方法,而這兩個方法的實現可能不一樣,這就不符合LSP原則。
(3)LSP原則是實現開閉原則的重要方式之一,由於使用基類對象的地方都可以使用子類對象,因此在程式中儘量使用基類類型來對對象進行定義,而在運行時再確定其子類類型,用子類對象來替換父類對象。
LSP原則簡而言之就是規範繼承時子類的一些書寫規則:
前置條件不能強化 後置條件不能弱化 不變數要保持或增強
子類型方法參數:逆變子類型方法的返回值:協變
異常類型:協變
這裡出現了兩個易混淆的名詞:協變和逆變,在下文將給出介紹。
二、協變和逆變
逆變與協變用來描述類型轉換(type transformation)後的繼承關係
定義:如果A、B表示類型,f(⋅)表示類型轉換,≤表示繼承關係
(比如,A≤B表示A是由B派生出來的子類)
f(⋅)是逆變(contravariant)的,當A ≤ B時有f(B) ≤ f(A)成立;
f(⋅)是協變(covariant)的, 當A ≤ B時有f(A) ≤ f(B)成立;
f(⋅)是不變(invariant)的, 當A ≤ B時上述兩個式子均不成立,即f(A)與f(B)相互之間沒有繼承關係。
1、協變
如果A是B的子類,那麼A中的類型T也是B中類型T’的子類,這就是協變。
父類型->子類型:越來越具體(specific)。
在LSP中,返回值和異常的類型:不變或變得更具體 。
2、逆變
如果A是B的子類,但是A中的類型T是B中類型T’的祖先類型,那麼就是逆變。
父類型->子類型:越來越抽象(abstract)。
參數類型:要相反的變化,不變或越來越抽象。