哪些代碼破壞了 LSP?

哪些代碼破壞了 LSP?

實際上,裏式替換原則還有另外一個更加能落地、更有指導意義的描述,那就是 Design By Contract,中文翻譯就是 按照協議來設計

子類在設計的時候,要遵守父類的行爲約定(或者叫協議)。父類定義了函數的行爲約定,那子類可以改變函數的內部實現邏輯,但不能改變函數原有的行爲約定。這裏的行爲約定包括:

  1. 函數聲明要實現的功能;

  2. 對輸入、輸出、異常的約定;

  3. 註釋中所羅列的任何特殊說明。

實際上,定義中父類和子類之間的關係,也可以替換成接口和實現類之間的關係。

爲了更好地理解這句話,我舉幾個違反裏式替換原則的例子來解釋一下。

子類違背父類聲明要實現的功能

父類中提供的 sortOrdersByAmount () 訂單排序函數,是按照金額從小到大來給訂單排序的,而子類重寫這個 sortOrdersByAmount ()訂單排序函數之後,是按照創建日期來給訂單排序的。那子類的設計就違背裏式替換原則。

子類違背父類對輸入、輸出、異常的約定

  1. 在父類中,某個函數約定:運行出錯的時候返回 null,獲取數據爲空的時候返回空集合(empty collection)。

  2. 子類重載函數之後,實現變了,運行出錯返回異常(exception),獲取不到數據返回 null。那子類的設計就違背裏式替換原則。

  3. 在父類中,某個函數約定,輸入數據可以是任意整數,但子類實現的時候,只允許輸入數據是正整數,負數就拋出,也就是說,子類對輸入的數據的校驗比父類更加嚴格,那子類的設計就違背了裏式替換原則。

在父類中,某個函數約定,只會拋出 ArgumentNullException 異常,那子類的設計實現中只允許拋出 ArgumentNullException 異常,任何其他異常的拋出,都會導致子類違背裏式替換原則。

子類違背父類註釋中所羅列的任何特殊說明

  1. 父類中定義的 withdraw () 提現函數的註釋是這麼寫的:“用戶的提現金額不得超過賬戶餘額……”

  2. 而子類重寫 withdraw () 函數之後,針對 VIP 賬號實現了透支提現的功能,也就是提現金額可以大於賬戶餘額,那這個子類的設計也是不符合裏式替換原則的。

以上便是三種典型的違背裏式替換原則的情況。

除此之外,判斷子類的設計實現是否違背裏式替換原則,還有一個小竅門,那就是拿父類的單元測試去驗證子類的代碼。如果某些單元測試運行失敗,就有可能說明,子類的設計實現沒有完全地遵守父類的約定,子類有可能違背了裏式替換原則。

實際上,你有沒有發現,裏式替換這個原則是非常寬鬆的。一般情況下,我們寫的代碼都不怎麼會違背它。所以,只要你能看懂我今天講的這些,這個原則就不難掌握,也不難應用。

雖然從定義描述和代碼實現上來看,多態和裏式替換有點類似,但它們關注的角度是不一樣的。

  1. 多態是面向對象編程的一大特性,也是面向對象編程語言的一種語法。它是一種代碼實現的思路。

  2. 裏式替換是一種設計原則,是用來指導繼承關係中子類該如何設計的,子類的設計要保證在替換父類的時候,不改變原有程序的邏輯以及不破壞原有程序的正確性。

文章已經同步更新到 Java 實驗室官方站點:

https://javawu.com/archives/2890

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Hj1PwLZCwnp3IJY9rApYQg