軟件架構設計原則之里氏替換原則


里氏替換原則(Liskov Substitution Principle,LSP)是指如果對每一個類型為T1的對象o1,都有類型為T2的對象O2,使得以T1定義的所有程序P在所有的對象O1都替換成O2時,程序P的行為沒有發生變化,那么類型T2是類型T1的子類型。

這個定義看上去還是比較抽象的,我們重新理解一下。可以理解為一個軟件實體如果適用於一個父類,那么一定適用於其子類,所有引用父類的地方必須能透明地使用其子類的對象,子類對象能夠替換父類對象,而程序邏輯不變。根據這個理解,引申含義為:子類可以擴展父類的功能,但不能改變父類原有的功能。

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

(2)子類可以增加自己特有的方法。

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

(4)當子類的方法實現父類的方法時(重寫/重載或實現抽象方法),方法的后置條件(即方法的輸出/返回值)要比父類更嚴格或與父類一樣。

在講開閉原則的時候我埋下了一個伏筆,在獲取折扣時重寫覆蓋了父類的getPrice()方法,增加了一個獲取源碼的方法getOriginPrice(),顯然就違背了里氏替換原則。我們修改一下代碼,不應該覆蓋getPrice()方法,增加getDiscountPrice()方法:

public class JavaDiscountCourse extends JavaCourse {

    public JavaDiscountCourse(Integer id, String name, Double price) {

        super(id, name, price);

    }

    public Double getDiscountPrice(){

        return super.getPrice() * 0.61;

    }

}

使用里氏替換原則有以下優點:

(1)約束繼承泛濫,是開閉原則的一種體現。

(2)加強程序的健壯性,同時變更時也可以做到非常好的兼容性,提高程序的可維護性和擴展性,降低需求變更時引入的風險。

現在來描述一個經典的業務場景,用正方形、矩形和四邊形的關系說明里氏替換原則,我們都知道正方形是一個特殊的長方形,所以就可以創建一個父類Rectangle:

public class Rectangle {

    private long height;

    private long width;

    @Override

    public long getWidth() {

        return width;

    }

    @Override

    public long getLength() {

        return length;

    }

    public void setLength(long length) {

        this.length = length;

    }

    public void setWidth(long width) {

        this.width = width;

    }

}

創建正方形類Square繼承Rectangle類:

public class Square extends Rectangle {

    private long length;

    public long getLength() {

        return length;

    }

    public void setLength(long length) {

        this.length = length;

    }

    @Override

    public long getWidth() {

        return getLength();

    }

    @Override

    public long getHeight() {

        return getLength();

    }

    @Override

    public void setHeight(long height) {

        setLength(height);

    }

    @Override

    public void setWidth(long width) {

        setLength(width);

    }

}

在測試類中創建resize()方法,長方形的寬應該大於等於高,我們讓高一直自增,直到高等於寬,變成正方形:

public static void resize(Rectangle rectangle){

    while (rectangle.getWidth() >= rectangle.getHeight()){

        rectangle.setHeight(rectangle.getHeight() + 1);

        System.out.println("width:"+rectangle.getWidth() + ",height:"+rectangle.getHeight());

    }

    System.out.println("resize方法結束" +

            "\nwidth:"+rectangle.getWidth() + ",height:"+rectangle.getHeight());

}

測試代碼如下:

public static void main(String[] args) {

    Rectangle rectangle = new Rectangle();

    rectangle.setWidth(20);

    rectangle.setHeight(10);

    resize(rectangle);

}

運行結果如下圖所示。

file

我們發現高比寬還大了,這在長方形中是一種非常正常的情況。現在我們把Rectangle類替換成它的子類Square,修改測試代碼:

public static void main(String[] args) {

    Square square = new Square();

    square.setLength(10);

    resize(square);

}

上述代碼運行時出現了死循環,違背了里氏替換原則,將父類替換為子類后,程序運行結果沒有達到預期。因此,我們的代碼設計是存在一定風險的。里氏替換原則只存在於父類與子類之間,約束繼承泛濫。我們再來創建一個基於長方形與正方形共同的抽象四邊形接口Quadrangle:

public interface Quadrangle {

    long getWidth();

    long getHeight();

}

修改長方形類Rectangle:

public class Rectangle implements Quadrangle {

    private long height;

    private long width;

    @Override

    public long getWidth() {

        return width;

    }

    public long getHeight() {

        return height;

    }

    public void setHeight(long height) {

        this.height = height;

    }

    public void setWidth(long width) {

        this.width = width;

    }

}

修改正方形類Square:

public class Square implements Quadrangle {

    private long length;

    public long getLength() {

        return length;

    }

    public void setLength(long length) {

        this.length = length;

    }

    @Override

    public long getWidth() {

        return length;

    }

    @Override

    public long getHeight() {

        return length;

    }

}

此時,如果我們把resize()方法的參數換成四邊形接口Quadrangle,方法內部就會報錯。因為正方形類Square已經沒有了setWidth()和setHeight()方法。因此,為了約束繼承泛濫,resize()方法的參數只能用Rectangle類。當然,我們在后面的設計模式的內容中還會繼續深入講解。

關注微信公眾號『 Tom彈架構 』回復“設計模式”可獲取完整源碼。

【推薦】Tom彈架構:30個設計模式真實案例(附源碼),挑戰年薪60W不是夢

本文為“Tom彈架構”原創,轉載請注明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術干貨!

其他設計原則

Tom彈架構:開閉原則(Open-Closed Principle,OCP)

Tom彈架構:依賴倒置原則(Dependence Inversion Principle,DIP)

Tom彈架構:單一職責原則(Simple Responsibility Pinciple,SRP)

Tom彈架構:接口隔離原則(Interface Segregation Principle, ISP)

Tom彈架構:迪米特原則(Law of Demeter LoD)

Tom彈架構:合成復用原則(Composite/Aggregate Reuse Principle,CARP)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM