里氏替換原則(Liskov Substitution Principle,LSP)是一個關於物件導向程式設計中子類別與父類別關係的原則。
聽起來非常的特別,而這一個原則,
也往往容易被違反的,
但就如同筆者一開始所說,
SOLID很重要,
但重點是違反是不是有他的必要性。
那麼LSP到底是什麼呢?
如果一個程式使用了一個父類別的物件,那麼用子類別的物件替換父類別的物件時,程式的行為功能不應該改變。也就是說,子類別應該完全遵循父類別的行為規範,不應該出現任何違反父類別期望的情況。
里氏替換原則可以用以下的數學公式來表示:
如果 S 是 T 的子型態,那麼對於任何型態為 T 的物件 x 和任何型態為 S 的物件 y,如果在所有針對 T 編寫的程式 P 中,用 y 替換 x 後,程式 P 的行為功能不變,那麼 S 就是 T 的子型態。
我相信有很多人都看不懂這個公式,
筆者也不怎麼喜歡寫這些公式,
那麼翻譯成文字會是三種條件。
子型態的先決條件(Preconditions)不應該被加強。
子型態的後置條件(Postconditions)不應該被削弱。
父型態的不變條件(Invariants)必須被子型態所保留。
按順序來解,何謂先決條件(Preconditions)呢?
先決條件是指在執行某個方法或動作之前,必須保證的狀態或條件。
可以想成如果要執行一件事情,
一定要某個狀態才能執行,
常見的例如需要能量才有辦法移動。
就以筆者所撰寫的程式來作為例子
public abstract class VehicleModel
{
//執行此程式,先決條件 Gasoline > 0
public abstract void Move();
//執行此程式,傳出值一定大於等於0
public int MovingDistance(int destination, int location)
{
return destination - location;
}
}
裡面有Move與MovingDistance,
其中Move就有先決條件,
需要能源才能移動。
假如筆者製作一個汽車class有去繼承並實現Move這個function,
但如果筆者讓Gasoline <= 0的狀況下執行Move,
甚至根本沒去設定Gasoline這個屬性
這就是先決條件加強。
至於程式中如何設定先決條件呢?
其實並沒有規定說一定要怎麼樣才叫先決條件,
只要能有辦法讓其他工程師知道,
要執行這個function前需要什麼條件,
就可以說是先決條件。
常見的如同註解、寫在function外、把此funcion包在規則function裡、防呆的錯誤訊息等等,
甚至於消極到如果你不照這個條件就會直接出現Error,
這些都能算是先決條件。
何謂後置條件(Postconditions)呢?
簡單來說就是符合某些條件的前提下,
可以保證輸出一定符合這個條件,
要注意的是符合某些條件的前提中的某些,
指的也有可能沒有條件。
也有人說Postconditions就是指對輸出的約束,
告訴使用者:「這是我承諾為你做的」
像是MovingDistance,
如果它的作用是一個移動距離的function,原先只會計算移動多遠,所以傳出沒有正負號,
如果繼承的子類別帶入了方向性而導致傳出有正負號,
那麼這個後置條件(Postconditions)會是增強。
那這種條件下並不會違反,
因為這個function,
承諾了計算移動距離,
所以這不會是違反。
但如果某個function的作用是里程表,細節為計算總共移動多少距離,所以沒有方向性,只會永遠增加。
如果繼承的子類別帶入了方向性而導致傳出有正負號,會出現增加減少,
那麼這個後置條件(Postconditions)會是增強還削弱?
答案會是削弱,就算是算式一樣,
但承諾的不同,會帶來不同的限制。
不變條件(Invariants)
簡單來說某個條件不能增強或削弱,
例如Move的時候一定有能源,
比較正式的說法就是
Invariants指的是在一個系統或一段程式中保持不變的性質或表達式。
不變條件可以用來描述系統或程式的狀態、限制、規則、對稱性等方面,幫助理解、設計、測試和驗證系統或程式的功能和正確性。
常見的有迴圈不變式(Loop Invariant)
---歷史
LSP由美國計算機科學家芭芭拉·利斯科夫(Barbara Liskov)在1987年提出,並在1994年與周以真(Jeannette Wing)共同發表了一篇論文來詳細說明這個原則。
https://zh.wikipedia.org/zh-tw/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99
里氏替換原則.維基百科