里氏替換原則
里氏替換原則的英文名稱是:Liskov Substitution Principle,簡稱LSP(老色批)。
1里氏替換原則的定義
英文定義有兩種:
①If for each object o1 of type S there is an object o2 of type T such thatfor all programs P defined in terms of S,the behavior of P is unchangedwhen o1 is substituted for o2 then T is a subtype of S.
這個定義是最正宗的定義,意思是:如果對一個類型為S的對象o1,都有類型為T的對象o2,使得以S定義的所有程序P在所有的對象o1都代換成o2時,程序P的行為沒有發生變化,那么類型T是類型S的子類型。
②Functions that use pointers or references to base classes must be ableto use objects of derived classes without knowing it.
第二個定義意思是:所有引用基類的地方必須能透明地使用其子類對象。清晰明確地說明只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用者可能根本就不需要知道父類還是子類;但是反過來則不可以,有子類的地方,父類未必就能適應。
里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。LSP 是繼承復用的基石,只有當派生類可以替換掉基類,且軟件單位的功能不受到影響時,基類才能真正被復用,而派生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對開閉原則的補充。實現開閉原則的關鍵步驟就是抽象化,而基類與子類的繼承關系就是抽象化的具體實現,
所以里氏代換原則是對實現抽象化的具體步驟的規范
。
繼承存在很多優點,但也存在缺陷,而
“里氏替換原則”可以減少"弊"所帶來的麻煩。
優點:
■ 代碼共享,減少創建類的工作量,每個子類都擁有父類的方法和屬性;
■ 提高代碼的可重用性;
■ 提高代碼的可擴展性;
■ 提高產品或項目的開放性。
缺陷:
■ 繼承是入侵式的。只要繼承,就必須擁有父類的所有屬性和方法;
■ 降低代碼的靈活性。子類必須擁有父類的屬性和方法,使子類受到限制;
■ 增強了耦合性。當父類的常量、變量和方法修改時,必須考慮子類的修改,這種修改可能造成大片的代碼需要重構。
2里氏替換原則的應用
在編譯期間,java語言編輯器會檢查一個程序是否符合里氏替換原則,這是一個無關實現的、純語法意義上的檢查。里氏替換原則要求是使用基類的地方,子類一定適用,因此子類必須具備基類的全部接口。或者說,子類型的接口必須包括全部的基類的接口,而且還有可能更寬。如果一個java程序破壞這一條件,java編輯器就會在編譯程序時拋出錯誤提示,並停止編譯。例如,一個基類Base聲明了一個public方法改換成private或procted。
即子類
不能使用一個
低訪問權限的方法
覆蓋基類中的
高訪問權限的方法。

里氏替換原則為良好的繼承定義了一個規范,它包含4層含義:
■ 子類必須完全實現父類的方法;
■ 子類可以有自己的個性;
■ 覆蓋或實現父類的方法時輸入參數可以被放大;
■ 覆蓋或實現父類的方法時輸出結果可以被縮小。
例:Animal 是一個表示動物的抽象類,只要是動物就都能動,因此提供一個抽象的move()方法;Horse和Bird都是Animal的子類。

下面編寫一個測試類

1 public class Test{ 2 public static void main(String[] args){ 3 //聲明一個基類對象 4 Animal animal; 5 //使用基類對象指向子類 6 animal = new Horse(); 7 animal.move; 8 animal = new Bird(); 9 animal.move; 10 // Horse h = new Animal(); //錯誤 11 } 12 }
上述代碼中,使用基類對象指向子類是允許的,但反過來,使用子類對象指向父類則違反里氏替換原則,會出現錯誤。
注意按照里氏替換原則,當多個類之間存在繼承關系時,通常應該使用父類或接口來指向子類的對象(除非需要使用子類特有的方法),這更利於提高系統的可擴展性。
在設計模式中體現里氏替換原則的有如下幾個模式:
■ 策略模式
■ 組合模式
■ 代理模式