Subtypes must be substitutable for their base types. (出自 Robert C. Martin aka Uncle Bob)
翻譯:基底類別應該要能夠被他的衍生類別給替代而不影響原本的功能
在今天的這個主題裡面我將要用實際例子來說明何謂 LSP
原則,也就是 里氏替換原則
經典範例 矩形 v.s. 正方形
在數學中我們可以知道 正方形
其實是一種長寬相等的 矩形
,這個概念其實都深植在我們的腦袋中,試想我們今天要把這個概念原封不動的透過物件導向的方式來進行實作,我們通常會直覺的定義以下的類別
1 | public class Rectangle |
再依照 正方形
是 矩形
的一種的概念,來建立正方形的物件,並且繼承 Rectangle 類別,但是因為我們知道正方形的長寬比例是 1:1,表示當我設定長時,寬也要設定完全一樣的長度,反之亦然,因此在 Square
類別中覆寫掉原本的 Height
及 Width
屬性
1 | public class Square : Rectangle |
接下來我們建立一個類別會去驗證 Rectangle 所計算出來的面積是不是如預期
1 | public class AreaVerifier |
接下來在主程式的地方呼叫兩次 VerifyArea
方法。為什麼要呼叫兩次呢,其實是呼應到一開始 LSP 所描述的原則
Subtypes must be substitutable for their base types.
1 | void Main() |
第一次先將 Rectangle
這個基底類別作初始化,然後丟給 AreaVerifier
類別中的 VerifyArea
方法來驗證,結果會回傳 True
,然後初始化 Rectangle
的衍生類別 Square
,在我們的認知中,衍生類別應該要能夠替換掉他的基底類別而不會影響原本的功能。
但是第二次我們將 Square
類別初始化並且設定其寬高,再將其丟給 AreaVerifier
類別中的 VerifyArea
方法來驗證,結果卻發生了 Exception。
1 | True |
為什麼會發生這樣的問題呢?
上面的範例也算是蠻極端的例子,但是可以凸顯出在物件導向的設計上與現實生活中所具有的盲點,因為我們一開始依照著當初靠著數學建立的觀念 正方形
是 長方形
的一種來設計我們的物件,雖說乍看之下是正確的,但是發生問題的點卻是在 行為
上的不同。
在這個例子中很明顯行為上不同的是 設定長和寬的行為有所差異
,所以其實在物件導向的設計上,有時候不能單純只靠直覺以及文字上的描述來進行,還須站在一個客觀的角度來觀察其開放給外部的行為是否也相同。
參考資料
- SOLID: Part 3 - Liskov Substitution & Interface Segregation Principles - Patkos Csaba
- 里氏替换原则(Liskov Substitution Principle)