S.O.L.I.D 物件導向設計原則 - LSP (Liskov Substitution Principle)

Posted by Ryan Tseng on 2017-10-16

Subtypes must be substitutable for their base types. (出自 Robert C. Martin aka Uncle Bob)

翻譯:基底類別應該要能夠被他的衍生類別給替代而不影響原本的功能

在今天的這個主題裡面我將要用實際例子來說明何謂 LSP 原則,也就是 里氏替換原則

經典範例 矩形 v.s. 正方形

在數學中我們可以知道 正方形 其實是一種長寬相等的 矩形,這個概念其實都深植在我們的腦袋中,試想我們今天要把這個概念原封不動的透過物件導向的方式來進行實作,我們通常會直覺的定義以下的類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Rectangle
{
private int _height;
public virtual int Height
{
get { return _height; }
set { _height = value; }
}

private int _width;
public virtual int Width
{
get { return _width; }
set { _width = value; }
}

public int Area()
{
return _height * _width;
}
}

再依照 正方形矩形 的一種的概念,來建立正方形的物件,並且繼承 Rectangle 類別,但是因為我們知道正方形的長寬比例是 1:1,表示當我設定長時,寬也要設定完全一樣的長度,反之亦然,因此在 Square 類別中覆寫掉原本的 HeightWidth 屬性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Square : Rectangle
{
private int _height;
private int _width;

public override int Height
{
get { return _height; }
set
{
_height = value;
_width = value;
}
}

public override int Width
{
get { return _width; }
set
{
_height = value;
_width = value;
}
}
}

接下來我們建立一個類別會去驗證 Rectangle 所計算出來的面積是不是如預期

1
2
3
4
5
6
7
8
9
public class AreaVerifier
{
public bool VerifyArea(Rectangle rect)
{
if (rect.Area() < 120)
throw new InvalidOperationException("Area must equals to 120");
return true;
}
}

接下來在主程式的地方呼叫兩次 VerifyArea 方法。為什麼要呼叫兩次呢,其實是呼應到一開始 LSP 所描述的原則

Subtypes must be substitutable for their base types.

1
2
3
4
5
6
7
8
9
10
11
12
13
void Main()
{
var verifier = new AreaVerifier();
var rect = new Rectangle();
rect.Width = 120;
rect.Height = 100;
verifier.VerifyArea(rect).Dump();

var square = new Square();
rect.Width = 120;
rect.Height = 100;
verifier.VerifyArea(square).Dump();
}

第一次先將 Rectangle 這個基底類別作初始化,然後丟給 AreaVerifier 類別中的 VerifyArea 方法來驗證,結果會回傳 True,然後初始化 Rectangle 的衍生類別 Square,在我們的認知中,衍生類別應該要能夠替換掉他的基底類別而不會影響原本的功能。

但是第二次我們將 Square 類別初始化並且設定其寬高,再將其丟給 AreaVerifier 類別中的 VerifyArea 方法來驗證,結果卻發生了 Exception。

1
2
True
Exception: Area must eqauls to 120

為什麼會發生這樣的問題呢?

上面的範例也算是蠻極端的例子,但是可以凸顯出在物件導向的設計上與現實生活中所具有的盲點,因為我們一開始依照著當初靠著數學建立的觀念 正方形長方形 的一種來設計我們的物件,雖說乍看之下是正確的,但是發生問題的點卻是在 行為 上的不同。

在這個例子中很明顯行為上不同的是 設定長和寬的行為有所差異,所以其實在物件導向的設計上,有時候不能單純只靠直覺以及文字上的描述來進行,還須站在一個客觀的角度來觀察其開放給外部的行為是否也相同。

參考資料