天天看點

寂然解讀設計模式 - 裡氏替換原則

I walk very slowly, but I never walk backwards            

設計模式原則 - 裡氏替換原則

​ 寂然

大家好,我是寂然~,本節課呢,我來給大家介紹設計模式原則之裡氏替換原則,話不多說,我們直接進入正題,老規矩,首先帶大家了解一下裡氏替換原則的官方定義,并作一個解釋,但是在此之前,我們先來聊聊ava面向對象最重要的特性之一 - 繼承性

前情提要 - 聊聊繼承性

繼承性相信大家已經十分熟悉了,繼承是面向對象的很重要的特性之一,其實我們今天課程要講的裡氏替換原則,就是要告訴我們,在程式設計中,如何正确的使用繼承,這裡有夥伴要問了,正确的使用怎麼解?OK,那我們先來聊聊,分析下繼承的優勢和劣勢

繼承優勢

● 提高代碼的複用性( 每個子類都擁有父類的方法和屬性 )

● 提高代碼的可擴充性( 很多開源架構的擴充接口都是通過繼承父類來完成的 )

繼承劣勢

● 繼承是侵入性的( 隻要繼承,就必須擁有父類的所有屬性和方法)

● 繼承機制很大的增加了耦合性( 如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮到所有的子類,并且父類修改後,所有涉及到子類的功能都可能産生故障)

上面提到了,裡氏替換原則,就是要告訴我們,在程式設計中,如何正确的使用繼承,帶着這樣的疑問,我們 先來看下裡氏替換原則的官方定義

官方定義

裡氏替換原則(Liskov Substitution Principle,LSP)是1988年,麻省理工學院一位姓裡的女士提出的,官方定義如下:

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

如果對每一個類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程式P在所有的對象o1都代換成o2時,程式P的行為沒有發生變化,那麼類型S是類型T的子類型

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

所有引用基類的地方必須能透明地使用其子類的對象

基本介紹

裡氏替換原則通俗的來講就是:子類可以擴充父類的功能,但不能改變父類原有的功能

其實繼承中包含這樣一層含義:父類中凡是已經實作好的方法,實際上是在設定規範和契約,雖然繼承不強制要求,所有的子類必須遵守這些契約,但是如果子類對這些已經實作的方法任意修改,就會對整個繼承體系造成破壞

上面我們提到,繼承給程式設計帶來便利的同時,也帶來了弊端,裡氏替換原則即是給繼承性制定了規範

案例示範 - 電腦

為了讓大家體會一下我們上面說的,我們通過一個案例來詳細說明一下

假設現在有一個電腦類,可以進行加法減法計算,我們定義其子類,進行需求的增補,簡易代碼如下:

//定義電腦類
class Calculator{

    //定義加法計算
    public int add(int a,int b){
        int result = a + b;
        return result;
    }

    //定義減法計算
    public int sub(int a,int b){
        int result = a - b;
        return result;
    }

}
//定義其子類
class HjCalculayor extends Calculator{

    //增補需求(兩數相加之和 +5) 無意中重寫了父類的方法
    public int add(int a,int b){
        int result = a + b + 5;
        return result;
    }

    //需求:二者相加之和,與100相減
    public int mul(int a,int b){

        int count = add(a, b);
        int result = 100 - count;
        return result;

    }
}           

OK,我們對上述代碼進行簡單的測試,可以看到,子類需要實作需求,無意間重寫了父類的方法

public static void main(String[] args) {

      int mulResult = new HjCalculayor().mul(2, 3);
      System.out.println("二者相加之和再與100相減的結果為" + mulResult);
      //運作結果:二者相加之和再與100相減的結果為90  出現問題

 }           

案例分析

我們發現原來運作正常的mul()方法發生了錯誤,原因就是子類 HjCalculayor 無意中重寫了父類的方法,造成原有功能出現錯誤,在實際程式設計中,我們常常會通過重寫父類的方法完成新的功能,這樣寫起來雖然簡單,但整個繼承體系的複用性會比較差,特别是運作多态比較頻繁的時候 ,針對上述問題,我們來聊聊解決方案

解決方案

上面出現的情況,其實就是裡氏替換原則擔心的,我們可以擴充,但是不能改變父類原有的功能,裡氏替換原則雖然這樣說,但并非讓我們因噎廢食,放棄使用繼承,我們可以通過其它方式來解決繼承所帶來的弊端,如:組合、聚合、依賴等方式,當然,這些後面在類關系中都會給大家展開深入講解

比如這裡,其中一種解決方案是讓原來的父類和子類都繼承一個更通俗的基類,原有的繼承關系去掉,如果類HjCalculayor 需要使用類 Calculator的方法,将二者變為組合關系來完成需求

//建立一個更加基礎的基類
//把更加基礎,需要複用的成員/方法寫到基類中
class Base{
   //TODO...
}

//定義電腦類
class Calculator extends Base{

    //定義加法計算
    public int add(int a,int b){
        int result = a + b;
        return result;
    }

    //定義減法計算
    public int sub(int a,int b){
        int result = a - b;
        return result;
    }

}

class HjCalculayor extends Base{

    //如果 HjCalculayor需要使用 Calculator 類的方法,使用組合關系
    private Calculator calculator = new Calculator();

    //增補需求(兩數相加之和 +5)
    public int add(int a,int b){
        int result = a + b + 5;
        return result;
    }

    //需求:二者相加之和,與100相減
    public int mul(int a,int b){

        int count = calculator.add(a, b);
        int result = 100 - count;
        return result;

    }
}
           

這樣可以看到,在完成業務邏輯時,明确調用 calculator.add() 方法,這樣既符合裡氏替換原則,子類避免改變父類原有的功能,同時定義一個更加通俗的基類,改變原有的繼承關系,也可以保證整個繼承體系的複用性

深度解析

裡氏替換原則其實還有以下兩個含義,我們一起來聊聊

一、子類可以實作父類的抽象方法,但是不能覆寫父類的非抽象方法

在我們做系統設計時,經常會設計接口或抽象類,然後由子類來實作抽象方法,這裡使用的其實也是裡氏替換原則,子類可以實作父類的抽象方法很好了解,事實上,子類也必須完全實作父類的抽象方法,哪怕寫一個空方法,否則會編譯報錯,裡氏替換原則的關鍵點在于不能覆寫父類的非抽象方法,這是他着重強調的

二、子類中可以增加自己特有的方法

在繼承父類屬性和方法的同時,每個子類也都可以有自己的個性,在父類的基礎上擴充自己的功能,前面其實已經提到,當功能擴充時,子類不要重寫父類的方法,而是另寫一個方法

注意事項

第一就是我們上面提到的,裡氏替換原則雖然指出了繼承帶來的一些弊端,但是并非讓我們放棄使用繼承,而是給我們制定了程式設計中正确使用繼承的規範,這是需要和大家再次強調的

第二,裡氏代換原則是實作開閉原則的重要方式之一,由于使用基類對象的地方都可以使用子類對象,是以在程式中盡量使用基類類型來對對象進行定義,而在運作時再确定其子類類型,用子類對象來替換父類對象

下節預告

OK,那既然上面提到了,裡氏代換原則是實作開閉原則的重要方式之一,那我們掌握了裡氏替換原則,下一節,我們正式進入開閉原則的學習,我會為大家用多個案例分析,來解讀設計模式原則之開閉原則,以及它的注意事項和細節,最後,希望大家在學習的過程中,能夠感覺到設計模式的有趣之處,高效而愉快的學習,那我們下期見~

繼續閱讀