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