里式替換原則——面向對象程式設計原則

  • 2019 年 11 月 4 日
  • 筆記

github倉庫

定義

Liskov於1987年提出了一個關於繼承的原則「Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.」—— 繼承必須確保超類所擁有的性質在子類中仍然成立.。通俗的來講就是子類可以擴展父類的功能,但是不能改變父類原有的功能。

該原則稱為Liskov Substitution Principle——里氏替換原則。

里氏替換原則主要闡述了有關繼承的一些原則,也就是什麼時候應該使用繼承,什麼時候不應該使用繼承,以及其中蘊含的原理。里氏替換原是繼承復用的基礎,它反映了基類與子類之間的關係,是對開閉原則的補充,是對實現抽象化的具體步驟的規範。

意義

  1. 防止重寫父類方法,出現父類復用性差的情況。

  2. 程式運行正確性的保證,即類的擴展不會給系統帶來新的錯誤,降低了出錯的可能性。因為子類重寫了父類方法,在使用多態特性時,程式可能會出現不可預知的錯誤。

做法

  • 子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法。

  • 子類中可以增加自己特有的方法。

  • 當子類的方法重載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬鬆。

  • 當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。

實踐

在英雄聯盟中,每個英雄到了6級都會擁有大招,大招的效果隨著英雄等級提升而提升,盲僧的大招有傷害,傑斯的大招只是切換形態,沒有傷害。我們在傑斯類中覆蓋了父類的方法,導致調用getrDPS()方法出現了意料之外的錯誤。

(後話系列)對於這種情況我們應該將Hero再細分為兩種子類,一種是大招有傷害類型,一種是無傷害類型,LeeSin和Jess分別繼承他們,將Hero做成頂級類,只擁有管理等級的功能。

uml圖

程式碼部分

英雄基類

/**   * 英雄基類   */  public class Hero {      /** R 技能傷害 */      private double rDPS;        /** 英雄等級 */      private int clas;        public void setClas(int clas){          this.clas = clas;      }        /**       * R技能伴隨等級的變化       */      public void getrDPS(){          rDPS = 2100 / clas * 3.97;          System.out.println("R技能的傷害為" + rDPS);      }  }

盲僧

/**   * 盲僧   */  public class LeeSin extends Hero{}

傑斯

/**   * 傑斯   */  public class Jess extends Hero{      @Override      public void setClas(int clas) {          System.out.println("雖然我到6了,但咱大招么得傷害呀,就不用傳值等級了,反正也沒用");      }  }

測試類

public class Main {      public static void main(String[] args) {          Hero leeSin = new LeeSin();          leeSin.setClas(6);          leeSin.getrDPS();            Hero jess = new Jess();          jess.setClas(6);          jess.getrDPS();      }  }

因為傑斯大招沒傷害,重寫了父類的setClas(int clas)方法沒有為clas賦值,故而在計算R技能傷害時發生了除零異常。

R技能的傷害為1389.5  雖然我到6了,但咱大招么得傷害呀,就不用傳值等級了,反正也沒用  Exception in thread "main" java.lang.ArithmeticException: / by zero