"圍觀"設計模式(2)--里氏替換原則(LSP,Liskov Substitution Principle)
阿新 • • 發佈:2019-01-27
在面向物件的程式設計中,里氏替換原則(Liskov Substitution principle)是對子型別的特別定義。它由芭芭拉·利斯科夫(Barbara Liskov)在1987年在一次會議上名為“資料的抽象與層次”的演說中首先提出。
里氏替換原則的內容可以描述為: “派生類(子類)物件能夠替換其基類(超類)物件被使用。” 以上內容並非利斯科夫的原文,而是譯自羅伯特·馬丁(Robert Martin)對原文的解讀。其原文為:
- Let be a property provable about objects of type. Then should be true for objects of type
芭芭拉·利斯科夫與周以真(Jeannette Wing)在1994年發表論文並提出的以上的Liskov代換原則。----維基百科
里氏替換原則我個人的理解是:在繼承關係中,父類的物件如果替換為子類的物件,他原來執行的行為依然保持不變,那麼這樣的程式才符合里氏替換原則,否則違背了里氏替換原則。
下面我們看這樣一個例項,體會下,里氏替換原則是在什麼情況下違背的。
一個簡單的繼承結構,在子類中,重寫父類的方法calc方法。
父類Calc:
package cn.design.pattern2016032004LiskovSubstitutionPrinciple; public class Calc { public void calc(int a, int b) { // a-b = ? System.out.println(a + " - " + b + " = " + (a - b)); } }
子類CalcSon,通過將父類中calc這個方法重寫為兩個數相加。
package cn.design.pattern2016032004LiskovSubstitutionPrinciple; public class CalcSon extends Calc{ public void calc(int a, int b) { // a+b = ? System.out.println(a + " + " + b + " = " + (a + b)); } // other method public void addThem(int a, int b) { System.out.println(a + b); } }
測試類:這裡如果符合里氏替換原則的話,那麼應該說將父類的呼叫的這個地方直接換為子類的話,那麼原來的行為不會發生任何的改變。但是下面的程式證明了,這樣的做法是違背了里氏替換原則的。將原先父類呼叫的替換為子類的時候,會由原來的父類的方法:減法,變為現在子類中的:加法。結果發生變化,從而違背了里氏替換原則。
Calc cal = new Calc();
cal.calc(10, 20);
/**
* 根據里氏替換原則,當父類替換為子類的時候,使用父類的時候的行為不應該
* 發生變化,那麼下面的這段程式碼,顯然發生了變化,這樣顯然違反了里氏替換
* 原則。
*/
Calc calcSon = new CalcSon();
calcSon.calc(10, 20);
我們在子類繼承父類之後,重寫了父類的方法時,需要注意,這樣的做法並不好,降低了整個繼承體系的複用性,出錯機率會相應的增加。
總結前人的諸多經驗來看,里氏替換原則主要是有四點:
1. 子類不要覆蓋父類的非抽象的方法。可以實現其抽象方法。
2. 子類可以實現自己獨有的方法。
3. 子類的方法重寫父類方法的時候,引數部分,要比父類的引數範圍要大或者等於(寬鬆)。釋義:舉個例子>如果說父類的方法中形參是ArrayList,那麼,其子類重寫這個方法的時候,形參要是List.
4. 子類重寫父類方法的時候,返回值要求,父類的返回值要比子類的返回值要小於或者等於。
面對這樣的情況,一般的,將當前的繼承結構解除掉,變為依賴或者聚合組合的形式。抽象出更高一層的抽象類,定義好這樣的一個抽象方法,同時由原先的兩個類繼承實現。
public abstract class Calculator {
public abstract void calc(int a, int b);
}
public class Calc extends Calculator{
public void calc(int a, int b) {
// a-b = ?
System.out.println(a + " - " + b + " = " + (a - b));
}
}
public class CalcSon extends Calculator{
public void calc(int a, int b) {
// a+b = ?
System.out.println(a + " + " + b + " = " + (a + b));
}
// other method
public void addThem(int a, int b) {
System.out.println(a + b);
}
}
通過這樣的途徑將原來的繼承結構重新解構重組後的繼承體系,應該說相對來說,出錯的機率大大降低了。原始碼已經上傳至GitHub:下載設計模式程式碼