天天看點

設計原則之【裡氏替換原則】

文章目錄

  • ​​什麼是裡氏替換原則​​
  • ​​裡氏替換原則與多态的差別​​
  • ​​執行個體​​
  • ​​哪些代碼違背裡氏替換原則​​
  • ​​子類修改了父類原有的功能邏輯​​
  • ​​子類違背父類的輸入輸出、異常的細節約定​​
  • ​​總而言之​​
  • ​​裡氏替換原則的意義​​
  • ​​參考資料​​

什麼是裡氏替換原則

子類對象(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(裡氏替換原則),那麼是不是考慮抽象或者設計出了問題。

裡氏替換最終一句話還是對擴充開放,對修改關閉,不能改變父類的入參,傳回,但是子類可以自己擴充方法中的邏輯。父類方法名很明顯限定了邏輯内容,比如按金額排序這種,子類就不要去重寫金額排序,改成日期排序之類的,而應該抽出一個排序方法,然後再寫一個擷取排序的方法,父類擷取排序調用金額排序,子類就重寫調用排序方法,擷取日期排序。

也是為了避免“二意性”,這裡是隻父類的邏輯和子類邏輯差别太多,讀代碼的人會感覺模棱兩可,父類一套,子類一套,到底應該讀哪種。感覺會混亂。

參考資料