Java設計模式——里氏代換原則

一、什麼是里氏代換原則?

一個軟件實體如果使用的是一個基類的話,那麼一定適用於其子類,而且它根本不能察覺出基類對象和子類對象的區別。比如,假設有兩個類,一個是Base類,另一個是Derived類,並且Derived類是Base類的子類。那麼一個方法如果可以接受基類對象Base的話:

method(Base b),那麼它必然可以接受一個子類對象d,也即method(d)。

里氏代換原則是繼承複用的基石。只有當衍生類可以替換掉基類,軟件單位的功能不會受到影響時,基類才能真正被複用,而衍生類也才能夠在基類的基礎上增加新的行爲。

二、從代碼重構的角度理解

里氏代換原則講的是基類和子類的關係。只有當這種關係存在時,里氏代換關係才存在;反之則不存在。如果有兩個具體類A和B之間的關係違反了里氏代換原則的設計,根據具體情況可以在下面的兩種重構方案中選擇一種:

(1)      創建一個新的類C,作爲兩個具體類的超類,將A和B的共同行爲移動到C中,從而解決A和B行爲不完全一致的問題,如下圖所示。


(2)      從B到A的繼承關係,改寫爲委派關係,如下圖所示。


正方形和長方形

一個長方形Rectangle類

public class Rectangle {
	private long width;
	private long height;
	public long getWidth() {
		return width;
	}
	public void setWidth(long width) {
		this.width = width;
	}
	public long getHeight() {
		return height;
	}
	public void setHeight(long height) {
		this.height = height;
	}
}

當width和height相等時,就得到了正方形對象。因此,長方形的對象中有一些是正方形對象。一個正方形Square類

public class Square {
	private long side;

	public long getSide() {
		return side;
	}

	public void setSide(long side) {
		this.side = side;
	}
}

因爲這個正方形類不是長方形的子類,而且也不可能成爲長方形的子類,因此在Rectangle類和Square類之間不存在里氏代換關係,如下圖所示。


正方形不可以作爲長方形的子類

爲什麼正方形不可以作爲長方形的子類呢?如果將正方形設置成長方形的子類,類圖


Square類的源代碼:

public class Square extends Rectangle{
	private long side;
	public long getWidth() {
		return getSide();
	}
	public void setWidth(long width) {
		setSide(width);
	}
	public long getHeight() {
		return getSide();
	}
	public void setHeight(long height) {
		setSide(height);
	}
	public long getSide() {
		return side;
	}

	public void setSide(long side) {
		this.side = side;
	}
}


這樣,只要width或height被賦值了,那麼width和height會被同時賦值,從而使長方形的長和寬總是相等的。但是如果客戶端使用一個Square對象調用下面的resize()方法時,就會得出與使用一個Rectangle對象不同的結論。當傳入的是一個Rectangle對象時,這個resize()方法會將寬度不斷增加,直到它超過長度纔會停下來。如果傳入的是一個Square對象,這個resize()方法會將正方形的邊不斷的增加下去,直到溢出爲止。換言之,里氏代換原則被破壞了,因此Square不應當成爲Rectangle的子類。

SmartTest類源代碼

public class SmartTest {
	public void resize(Rectangle r){
		while(r.getWidth()<=r.getHeight()){
			r.setWidth(r.getWidth()+1);
		}
	}
}

代碼的重構

Rectangle類和Square類到底是怎樣的關係呢?它們都應當屬於四邊形(Quadrangle)的子類,通過發明一個Quadrangle(四邊形)類,並將Rectangle類和Square類變成它的具體子類,就解決了Rectangle類和Square類的關係不符合里氏代換原則的問題。如下圖所示。


Java接口Quadrangle(四邊形)類的代碼,其中只聲明瞭兩個取值方法,沒有聲明任何的賦值方法。

public interface Quadrangle {
	public long getWidth();
	public long getHeight();
}

長方形是四邊形的子類,具有賦值方法。Rectangle類。

public class Rectangle implements Quadrangle{
	private long width;
	private long height;
	public long getWidth() {
		return width;
	}
	public void setWidth(long width) {
		this.width = width;
	}
	public long getHeight() {
		return height;
	}
	public void setHeight(long height) {
		this.height = height;
	}
}

正方形是四邊形的子類,具有賦值方法。Square類。

public class Square implements Quadrangle{
	private long side;
	public long getSide() {
		return side;
	}
	
	public void setSide(long side) {
		this.side = side;
	}
	public long getWidth() {
		return getSide();
	}
	public long getHeight() {
		return getSide();
	}
}

破壞里氏代換原則的問題是怎麼避免的呢?祕密在於基類Quadrangle類沒有賦值方法。因此上面的resize()方法不可能適用於Quadrangle類型,而只能適用於不同的具體子類Rectangle和Square,因此里氏代換原則不可能被破壞。

發佈了11 篇原創文章 · 獲贊 10 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章