首頁 » 物件導向 » [SOLID原則(三)] 里氏替換原則(LSP)

[SOLID原則(三)] 里氏替換原則(LSP)

作者:

分類:

甚麼是SOLID?

SOLID,是由Robert C. Martin等人提出的5個物件導向設計原則的英文字首組成,分別為單一職責(S)開閉原則(O)、里氏替換(L)、介面隔離(I)以及依賴反轉(D)。SOLID提供程式開發者良好的設計指引,可以開發出易理解、易維護、易拓展的系統。

里氏替換原則(Liskov Substitution Principle,LSP)

Liskov Substitution Principle,LSP

“Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type S where S is a subtype of T.” – B. Liskov, J. Wing

譯文:如果 S 是 T 的子類型,則適用於 T 物件的內容也適用於 S 物件。

定義不是很好懂,參考很多文章後,自己大概理解為「子類如果屬於父類,則父類應也可以替換成子類,並保持原有功能的正確」。意思是強調繼承時不應修改父類別原有的功能(也可以說是架構),並需要實作所有抽象方法

有個很經典的例子是”長方形不是正方形”,如果讓長方形去繼承正方形,就不符合長寬等長的限制了。

以C#多型為例

C#在多型應用時,常見的abstract/virtual…等,編譯器會有明確的限制,例如:

  • abstract類別或方法只能定義不能實例化
  • virtual方法雖然可在父類別實作,但子類別可以覆寫它
  • 沒有定義abstract/virtual的方法則不可覆寫

多多利用這些修飾詞去設計程式框架是重要的。

舉例

最常見的例子是把父類作為容器,像是List<Shape>,我只需要計算各種形狀面積的功能,不會把Rectangle長寬欄位放在Shape內,否則遇到Polygon、Triangle…等不同形狀就會發生問題。

abstract class Shape
{
    public abstract int GetArea();
}

class Rectangle : Shape
{
    int width, height;
    public override int GetArea() { DoSomething... }
}

class Polygon : Shape
{
    public override int GetArea() { DoSomething... }
}
var shapes = new List<Shape>();
foreach(item in shapes)
{
    Console.WriteLine(item.GetArea());
}

不過度依賴繼承

各種四邊形
各種四邊形

繼承雖然可以進行拓展及重複利用,但不是沒有缺點。承上方的例子,如果寫一個含有長寬及計算面積的類別給正方形、長方形繼承,如果遇到平行四邊形或是梯形呢?是不是就顯得這類別有點多餘。

退一步海闊天空,以宏觀角度去看整個架構,了解這類別需要的功能,會發現有些程式碼不一定是必要的。如果繼承類別的過程因為耦合程度太高,無法符合LSP,可以考慮interface去抽出部分功能來降低耦合,或是重新檢視類別之間的關係。

結論

里氏替換原則提供了物件階層關聯之間的約定”子類不能修改父類功能“,不能覆寫非abstract/virtual的方法,另外在覆寫abstract/virtual時要注意不能破壞父類原本的功能。

另外在LSP在物件導向設計就像是生物學的生物分類法,定義了界門綱目科屬種,從上到下定義會越來越嚴格,就像是人類跟獼猴都屬於靈長目,所以人與猴都會繼承自靈長目,且不會違反其定義,否則就會需要重新檢視了。

參考資料

「cian」的個人頭像

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *