一、什麼是里氏代換原則?
一個軟件實體如果使用的是一個基類的話,那麼一定適用於其子類,而且它根本不能察覺出基類對象和子類對象的區別。比如,假設有兩個類,一個是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,因此里氏代換原則不可能被破壞。