文章目錄
- 什麼是裡氏替換原則
- 裡氏替換原則與多态的差別
- 執行個體
- 哪些代碼違背裡氏替換原則
- 子類修改了父類原有的功能邏輯
- 子類違背父類的輸入輸出、異常的細節約定
- 總而言之
- 裡氏替換原則的意義
- 參考資料
什麼是裡氏替換原則
子類對象(object of subtype/derived class)能夠替換程式(program)中父類對象(object of base/parent class)出現的任何地方,并且保證原來程式的邏輯行為(behavior)不變及正确性不被破壞。
裡氏替換原則與多态的差別
實作多态的條件:
1.繼承:必須要有子類繼承父類的繼承關系。
2.重寫:子類需要對父類中的一些方法進行重寫,然後調用方法時就會調用子類重寫的方法而不是原本父類的方法。
3.向上轉型:在多态中需要将子類的引用賦給父類對象,隻有這樣該引用才能夠具備技能調用父類的方法和子類的方法。
多态的核心概念是向上轉型,這是實作多态的核心。
而裡氏替換原則講的是,子類不能改變父類原來的邏輯,子類完美繼承父類的設計初衷,并做了增強。
這兩者的出發點是不同的。
執行個體
我們舉個例子,LoginLog類繼承了原來的Login類,并判斷是否需要列印登入日志。
public class Login{
public Response doLogin(Request request) {
// ...登入邏輯
}
}
public class LoginLog extends Login {
private Boolean openDebug;
public LoginLog(Bookean openDebug) {
this.openDebug= openDebug;
}
@Override
public Response doLogin(Request request) {
if (openDebug == true) {
logger.log("xxxx進行了登入");
}
return super.sendRequest(request);
}
}
public class Demo {
public void demoFunction(Login login) {
Reuqest request = new Request();
//...省略設定request中資料值的代碼...
Response response = login.doLogin(request);
//...省略其他邏輯...
}
}
// 裡式替換原則
Demo demo = new Demo();
demo.demofunction(new LoginLog(true););
在上面的代碼中,子類 LoginLog的設計完全符合裡式替換原則,可以替換父類出現的任何位置,并且原來代碼的邏輯行為不變且正确性也沒有被破壞。
乍一看上去,這不就是多态嘛,其實,裡氏替換原則跟多态的核心思想是不同的,我們繼續往下看,假如說LoginLog的代碼稍作修改,改成下面這個樣子:
public class LoginLog extends Login {
private Boolean openDebug;
public LoginLog(Bookean openDebug) {
this.openDebug= openDebug;
}
@Override
public Response doLogin(Request request) {
if (openDebug == false) {
throw new RuntimeException(...);
}
logger.log("xxxx進行了登入");
return super.sendRequest(request);
}
}
LoginLog繼承了Login之後,将doLogin核心代碼做了一些改變,使其部分入參的響應結果與父類不同。雖然LoginLog并不會帶來編譯上的錯誤,但是在設計上,我們認為它不符合裡氏替換原則的。
多态和裡式替換有點類似,但它們關注的角度是不一樣的。多态是面向對象程式設計的一大特性,也是面向對象程式設計語言的一種文法。它是一種代碼實作的思路。而裡式替換是一種設計原則,是用來指導繼承關系中子類該如何設計的,子類的設計要保證在替換父類的時候,不改變原有程式的邏輯以及不破壞原有程式的正确性。
哪些代碼違背裡氏替換原則
子類修改了父類原有的功能邏輯
比如說,訂單排序,父類是按照時間正序排列,子類按照時間倒叙排列。
比如說,父類查詢出使用者的機構,子類卻給去掉了。
比如說,父類買賣商品時不允許超額售賣,子類卻允許了超額售賣
子類違背父類的輸入輸出、異常的細節約定
比如說,父類對于特定條件下輸出為null,子類修改了這個邏輯,輸出不為null了。
比如說,父類對于輸入一個正整數,抛出一個異常,子類将這個異常捕獲了。
總而言之
隻要是父類的一切輸入、輸出、異常、邏輯,子類有任何的修改,就是違背裡氏替換原則。
子類不能改變父類原來的邏輯,子類完美繼承父類的設計初衷,并做了增強。
裡氏替換原則的意義
一、改進已有實作。例如程式最開始實作時采用了低效的排序算法,改進時使用LSP(裡氏替換原則)實作更高效的排序算法。
二、指導程式開發。告訴我們如何組織類和子類(subtype),子類的方法(非私有方法)要符合contract。
三、改進抽象設計。如果一個子類中的實作違反了LSP(裡氏替換原則),那麼是不是考慮抽象或者設計出了問題。
裡氏替換最終一句話還是對擴充開放,對修改關閉,不能改變父類的入參,傳回,但是子類可以自己擴充方法中的邏輯。父類方法名很明顯限定了邏輯内容,比如按金額排序這種,子類就不要去重寫金額排序,改成日期排序之類的,而應該抽出一個排序方法,然後再寫一個擷取排序的方法,父類擷取排序調用金額排序,子類就重寫調用排序方法,擷取日期排序。
也是為了避免“二意性”,這裡是隻父類的邏輯和子類邏輯差别太多,讀代碼的人會感覺模棱兩可,父類一套,子類一套,到底應該讀哪種。感覺會混亂。