1. 程式人生 > >"圍觀"設計模式(2)--里氏替換原則(LSP,Liskov Substitution Principle)

"圍觀"設計模式(2)--里氏替換原則(LSP,Liskov Substitution Principle)

面向物件的程式設計中,里氏替換原則(Liskov Substitution principle)是對子型別的特別定義。它由芭芭拉·利斯科夫(Barbara Liskov)在1987年在一次會議上名為“資料的抽象與層次”的演說中首先提出。

里氏替換原則的內容可以描述為: “派生類(子類)物件能夠替換其基類(超類)物件被使用。” 以上內容並非利斯科夫的原文,而是譯自羅伯特·馬丁(Robert Martin)對原文的解讀。其原文為:

Let q(x) be a property provable about objectsx of typeT. Thenq(y) should be true for objectsy of typeS
whereS is a subtype ofT.

芭芭拉·利斯科夫與周以真(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:下載設計模式程式碼