2011年4月12日 星期二

Design Principle - Liskov's Substitution Principle (LSP)

簡介

LSP指得是在程式中使用base class(基礎類別)的地方,
都能夠改用其derived class(沿生類別),
而且不會讓程式出現非預期的結果(例如RuntimeException),
也不會影響程式原本的功能。

亦即,derived class能夠做為base class的substitution(替代品)。

反面範例

最常見的例子,就是正方形與矩形之間的關係。

數學上來說,正方形是一種(is-A)矩形,

假設有個數學系統,
須新增一個能夠計算矩形面積的程式,
會直覺地設計成:



在系統中,設計了printArea method,用來印出矩形的面積:


當系統增加正方形時,會設計成:


當系統要計算Square的面積時,一樣透過printArea method:


上述程式翻譯成中文是:

建立一個正方形(是一種矩形),
設定長度為9,設定寬度為8,
(與正方形特性有所抵觸,因為正方形四邊一樣長,只要設定一個邊即可)
印出面積為64。

這樣的執行結果並不在使用者的預期中,
因為9 * 8 = 72 != 64

使用者只知道resize method接收Rectangle型態的物件,
即使傳入的物件為Square,
使用者預設仍然會認為setWidth與setHeight不會互相影響。

這就造成了問題。

因此,在進行物件導向設計時,不要違反了LSP,
也就是Liskov's Substitution Principle。

正面範例

在改善上述例子的方式前,
先思考Square和Rectangle之間的關係,
以及程式的目的(印出面積)。
針對上面兩點,可以歸納出,
1. Square只須要設定一個邊
2. Rectangle要設定二個邊
3. 兩者都須要印出面積

因此,改善的方式為:

Sqaure不用去繼承Rectangle(雖然數學老師會說正方形是一種矩形)
而是讓Square和Rectangle都去實作Shape interface:


然後以建構子的方式,傳入Rectangle的長寬和Square的邊。


之後就能夠正常地使用Rectangle與Square:


改善過後,
Rectangle維持長與寬,而Square只須要設定邊長
一來Rectangle與Square變得更明確,
二來程式也變得容易擴充。

結論

在設計程式時,須考慮base class與dervied class之間的關係,
不恰當的base class會影響dervied class,使其出現不必要的method(參考ISP),
同樣地,實作dervied class時,也必須思考到是否符合LSP,
避免程式發生非預期的問題。

沒有留言:

張貼留言