1. 程式人生 > >Java設計模式——里氏代換原則

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,因此里氏代換原則不可能被破壞。