Liskov Substitution Principle
Derived classes must be substitutable for their base classes.
對於父類別或者interface 出現的地方,都可以透過子類別或者該interface 的實作來取代,而不能破壞原有的行為
舉個例子來說,我們假設有一個取得所有課程的類別,而取得課程的方式可以透過Database 或者FileSystem 取得,因此我們可以將類別規劃如下:
1 | interface LessonRepositoryInterface |
我們可以透過以下的方法取得我們想要的課程:
1 | function foo(LessonRepositoryInterface $lesson) |
但這顯然是一個很糟糕的實踐方法,因為已經違反了OCP 原則,還不曉得OCP 原則的話可以參考筆者的前一篇文章。
當破壞LSP 原則的同時反而會讓你的程式碼在實踐的過程中同時破壞OCP 原則,因為 FileLessonRepository
以及 DBLessonRepository
,這兩個class 所回傳的 DataType
不相同,進而導致取得資料時必須依據取得的類型而有不同的處理方式。
因此我們先將所實踐的類別改成以下寫法:
1 | class FileLessonRepository implements LessonRepositoryInterface |
將方法回傳的值全部限定為 array
,在實踐上就不需要預期有不同的回傳結果
1 | function foo(LessonRepositoryInterface $lesson) |
總結一下,在不違反LSP 原則的前提下,所設計的 class
需遵守以下四點原則:
- Signature must match.
- Preconditions can’t be greater.
- Postconditions at least equal to.
- Exception types must match.
譯:
- 子類別必須完全實現父類別的方法
- 每個方法使用前,必須檢驗傳入參數的正確性,例如
int
及double
- 執行的結果必須符合
contracts
,例如回傳的參數型態 - 拋出的例外狀況必須相同