“一個軟件實體如果使用的是一個基類的話,一定適用於其子類,而且根本不能覺察出基類對象和子類對象的區別。”
陳述:
- 子類型(Subtype)必須能夠替換他們的基類型(Basetype)
若對每個類型S的對象o1,都存在一個類型T的對象o2,使得在所有針對T編寫的程序P中,用o1替換o2後,程序P的行爲功能不變,則S是T的子類型。
通俗地講,就是子類型能夠完全替換父類型,而不會讓調用父類型的客戶程序從行爲上有任何改變。
意義:
我們在客戶程序在調用某一個類時,實際上是對該類的整個繼承體系設定了一套約束,繼承體系中的所有類必須遵循這一約束,即前置條件和後置條件必須保持一致。這爲對象繼承加上了一把嚴格的枷鎖。顯然,LSP原則對於約束繼承的泛濫具有重要意義。
- 違反這個職責將導致程序的脆弱性和對OCP的違反
- f(&d) 會導致錯誤
- 如果我們試圖編寫一些測試,以保證把d傳給f時可以使f具有正確的行爲。那麼這個測試違反了OCP——因爲f無法對Base的所有派生類都是封閉的。
class Rectangle { private: long width; long height; public: void setWidth(long width) { this->width = width; } long getWidth() { return this->width; } void setHeight(long height) { this->height = height; } long getHeight() { return this->height; } }; //正方形類 class Square { private: long side; public: void setSide(long side) { this->side = side; } long getSide() { return side; } };
//正方形類(如果繼承自長方形類):
class Square : public Rectangle
{
private:
long side;
public:
void setWidth(long width)
{
setSide(width);
}
long getWidth()
{
return getSide();
}
void setHeight(long height)
{
setSide(height);
}
long getHeight()
{
return getSide();
}
long getSide()
{
return side;
}
void setSide(long side)
{
this->side = side;
}
};
class SmartTest
{
public:
void resize(Rectangle r)
{
while (r.getHeight() <= r.getWidth() )
{
r.setWidth(r.getWidth() + 1);
}
}
};
里氏代換與通常的數學法則和生活常識有不可混淆的區別。
考慮一個設計是否恰當時,不能孤立的看待並判斷,應該從此設計的使用者所作出的假設來審視它!
這個看似明顯正確的模型怎麼會出錯呢?
“正方形是一種長方形”
對不是SmartTest函數的編寫者而言,正方形可以是長方形,但是對SmartTest函數的編寫者而言,Square絕對不是Rectangle!!
class Quadrangle
{
public:
virtual long getWidth() = 0;
virtual long getHeight() = 0;
};
Quadrangle類只聲明兩個取值方法,不聲明任何的賦值方法。class Rectangle : public Quadrangle
{
private:
long width;
long height;
public:
void setWidth(long width)
{
this->width = width;
}
long getWidth()
{
return this->width;
}
void setHeight(long height)
{
this->height = height;
}
long getHeight()
{
return this->height;
}
};
//正方形類:class Square : public Quadrangle
{
private:
long side;
public:
void setSide(long side)
{
this->side = side;
}
long getSide()
{
return side;
}
long getWidth()
{
return getSide();
}
long getHeight()
{
return getSide();
}
};
問題如何得以避免?基類Quadrangle類沒有賦值方法,因此類似於 SmartTest的resize()方法不可能適用於Quadrangle類型,而只能適用於不同的具體子類Rectangle 和Square,因此里氏替換原則不可能被破壞。
結論:
- 儘量從抽象類繼承,而不從具體類繼承。
- 如果有兩個具體類A和B有繼承關係,那麼一個最簡單的修改方案應當是建立一個抽象類C,讓類A和B成爲抽象類C的子類。
- 更進一步: 如果有一個由繼承關係形成的等級結構的話,那麼在等級結構的樹圖上面所有的樹葉節點都應該是具體類,而所有的樹枝節點都應該是抽象類或接口。
- Strategy
- Composite
- Proxy
參考資源:
《設計模式:可複用面向對象軟件的基礎》,ERICH
GAMMA RICHARD HELM RALPH JOHNSON JOHN VLISSIDES著作,李英軍 馬曉星 蔡敏 劉建中譯,機械工業出版社,2005.6
《敏捷軟件開發:原則、模式與實踐》,Robert C. Martin著,鄧輝譯,清華大學出版社,2003.9
《設計模式解析》,Alan Shalloway等著(徐言聲譯),人民郵電出版社,2006.10