里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。

原則

第一點

子類必須實現父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實現)方法。

class Foo {
    public void cal(int num1, int num2) {
        int value = num1 + num2;
        System.out.println("父類計算結果: " + value);
    }
}

class Son extends Foo {
    public void cal(int num1, int num2) {
        int value = num1 - num2;
        System.out.println("子類計算結果:" + value);
    }
}

class Cal{
    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.cal(2,1);
        Son son = new Son();
        son.cal(2,1);
    }
}

在類的繼承中,我們的父類定義好的方法,並不會強制要求其子類必須完全遵守該方法的實現規則。子類是可以修改它繼承自父類的任意方法的。

在本例中,父類的本意是想要定義一個兩數相加的方法,但是子類繼承該方法後卻修改爲減法,並且也成功了。子類這樣操作後,會對整個繼承體系造成破壞。當你想把使用父類的地方替換爲其子類時,會發現原來的正常的功能現在出現問題了。

第二點

當子類需要重載父類中的方法的時候,子類方法的形參(入參)要比父類方法輸入的參數更寬鬆(範圍更廣)。

class Foo {
    public void method(List arrayList) {
        System.out.println("父類方法執行");
    }
}

class Son extends Foo {
    public void method(ArrayList list) {
        System.out.println("子類方法執行" );
    }
}

class Cal{
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        Foo foo = new Foo();
        Son son = new Son();
        System.out.println("使用父類對象調用的結果:");
        foo.method(list);
        System.out.println("將父類對象替換爲子類對象調用結果");
        son.method(list);
    }
}

//輸出
使用父類對象調用的結果:
父類方法執行
將父類對象替換爲子類對象調用結果
子類方法執行

我們的本意是希望對象替換後還執行原來的方法的,可結果卻發生變化了。

修改

class Foo {
    public void method(ArrayList arrayList) {
        System.out.println("父類方法執行");
    }
}

class Son extends Foo {
    //重載了父類的method,並且方法入參比父類的入倉範圍更廣
    public void method(List list) {
        System.out.println("子類方法執行" );
    }
}

第三點

重寫或者實現父類方法的時候,方法的返回值可以被縮小,但是不能放大。

正例:

class Foo {
    public List getList() {
        return new ArrayList();
    }
}

class Son extends Foo {
    public ArrayList getList() {
        return new ArrayList();
    }
}

反例:

class Foo {
    public ArrayList getList() {
        return new ArrayList();
    }
}

class Son extends Foo {
    public List getList() {
        return new ArrayList();
    }
}

如果我們試圖在子類中放大,重寫或實現來自父類方法的返回值時,代碼會報錯,連基本的編譯器都無法通過。

第四點

子類可以擁有自己獨特的方法或屬性

class Foo {
    public void cal(int num1, int num2) {
        int value = num1 + num2;
        System.out.println("父類計算結果: " + value);
    }
}

class Son extends Foo {
    public void cal(int num1, int num2) {
        int value = num1 - num2;
        System.out.println("子類計算結果:" + value);
    }
    public void cal2(int num1, int num2) {
        int value = num1 + num2 +num2;
        System.out.println("子類計算結果:" + value);
    }
}

總結

通過上面的描述相信大家都對里氏替換原則有了一個基本的概念,其實它就是告訴我們在繼承中需要注意什麼問題和遵守什麼規則。

然而在實際開發中我們在很多時候還是會違背該原則的,雖然表面上沒有什麼特別大的問題,但是這樣做會大大增加代碼的出錯率。我們編寫代碼時不光要考慮怎麼實現該功能,程序的健壯性和後期的擴展以及移植都是需要考慮到的。只有這樣做纔可以使我們的程序更加優秀。

相關文章