Feb. 13 12:08~12:52
Liskov Substitution Principle(LSP,里氏替換原則)和Design By Contract(DBC,依合約設計)所說的Subcontracting(子合約)兩者是一樣的東西。上周六在上「敏捷開發懶人包:物件導向技能」課程的時候Teddy引用了《Agile Software Development》書中LSP的例子:
▼假設某人做了一個物件導向設計,讓Square(正方形)繼承Rectangle(長方形)。
▼Rectangle的程式碼如下。
▼以下是Square的程式碼,因為正方形的四邊相等,所以Square類別覆寫(override)Rectangle的setWidth與setHeight這兩個method,無論是設定正方型的寬或高,需要同時把高或寬設成一樣。
▼上述設計到目前為止看起來沒什麼問題,接下來看兩個測試案例。testRectangleArea()設定長方形的高和寬分別是5,4,所以得到面積為20。依照Liskov Substitution Principle的定義:「系統中基本型別(base types)出現的地方,都可以用子型別( subtypes)來取代,而不會破壞程式原有的行為。」所以在testSquareArea()測試案例中,第23行把 r 設定為 Square的instance,因為Square是Rectangle的子類別,如果符合LSP原則,那麼Square不會破壞原有程式的行為,也就是說第27行計算面積的結果應該還是20。
▼但鄉民們一看就知道第二個測試案例會失敗,因為正方形的面積應該是16而不是20。也就是說在這個例子中,基本型別(Rectangle)出現的地方用子型別( Square)來取代的結果破壞程式原有的行為,因此違反了LSP。
***
因為Teddy上課的時候是從Design by Contract的角度來解釋LSP,因此有學員問:「這個例子要如何從合約的角度來判斷?」很簡單:
▼Rectangle的setHeight的postcondition:
this.height = height;
this.width = old width;
▼Square的setHeight的postcondition:
this.height = height;
this.width = height;
▼依據DBC的Subcontracting原則,子類別的postcondition需要與父類別的postcondition進行AND運算
▼所以Square的setHeight的postcondition實際上應該是:
this.height = height;
this.width = old width;
AND
this.height = height;
this.width = height;
很顯然this.width = old width;和this.width = height;這兩條合約無法同時被滿足,所以Square的行為無法與其父類別相容,因此違反了Subcontracting原則。
***
友藏內心獨白:合約可以寫在程式碼中,也可寫在測試案例中。
所以,應該是讓Rectangle(長方形)繼承Square(正方形)??
回覆刪除兩者行為不相容,所以也許可以另外建一個Shape類別,讓Rectangle和Square都繼承自Shape。
回覆刪除