六大設計原則(二)LSP里氏替換原則

来源:https://www.cnblogs.com/quinntian/archive/2019/04/19/10739178.html
-Advertisement-
Play Games

里氏替換原則LSP (Liskov Subsituation Principle) 里氏替換原則定義 所有 父類出現 的地方可以使用 子類替換 並不會出現錯誤或異常,但是反之子類出現的地方不一定能用父類替換。 LSP的四層含義 子類必須完全實現父類的方法 子類可以自己的個性(屬性和方法) 覆蓋或實現 ...


里氏替換原則LSP(Liskov Subsituation Principle)

里氏替換原則定義

所有父類出現的地方可以使用子類替換並不會出現錯誤或異常,但是反之子類出現的地方不一定能用父類替換。

LSP的四層含義

  • 子類必須完全實現父類的方法
  • 子類可以自己的個性(屬性和方法)
  • 覆蓋或實現父類的方法時輸入參數可以被放大
  • 覆蓋或實現父類的方法時輸出結果可以被縮小

LSP的定義含義1——子類必須完全實現父類的方法

假設如下場景:定義一個槍支抽象類,一個場景類,三個槍支實現類,一個士兵類。此處,三個槍支完全實現了父類的方法。

關聯關係:實線箭頭
泛化關係:實線空心箭頭(繼承關係)
依賴關係:虛線箭頭(使用關係)一個類需要另一個類的協助

file
抽象槍支類:射擊功能

package des.lsp;

/**
 * 抽象類 槍支
 */
abstract class AbstractGun {
    //射擊功能
  public abstract void shoot();
}

子類實現

package des.lsp;

/**
 * 手槍
 */
public class HandGun extends AbstractGun {
    @Override
    public void shoot() {
        System.out.print("手槍可以射擊");
    }
}
package des.lsp;

/**
 * 手槍
 */
public class MachineGun extends AbstractGun {
    @Override
    public void shoot() {
        System.out.print("步槍可以射擊");
    }
}
package des.lsp;

/**
 * 步槍
 */
public class Rifle extends AbstractGun {
    @Override
    public void shoot() {
        System.out.print("步槍可以射擊");
    }
}

士兵類:士兵類使用的是抽象槍支類,具體的需要在場景類中指定。

類中調用其他類必須使用父類或介面,若不能使用則其實已經違背了LSP原則。

package des.lsp;

public class Soldier {
    private AbstractGun gun;
    public void setGun(AbstractGun _gun){
        this.gun = _gun;
    };
    public void killEnemy(){
        System.out.print("士兵開始殺人...");
        gun.shoot();
    }

}

場景類

package des.lsp;

public class Client {
    public static void main(String[] args) {
        // write your code here
        Soldier s = new Soldier();
        s.setGun(new Rifle());
        s.killEnemy();
    }
}

如果加入一個玩具槍類,即玩具槍類同樣繼承抽象槍支類,此時就會存在子類不能實現槍支類方法的情況,因為玩具槍和槍最本質的區別是玩具槍不能射擊的,是無法殺死人的。但是,玩具槍的其他屬性,比如顏色等一些屬性可以委托抽象槍支類進行處理。

如果子類不能完整的實現父類的方法或者父類某些方法在子類中發生了畸變,則應該斷開父子關係採用依賴、組合、聚集等關係來代替原有的繼承。

玩具槍繼承槍支抽象類的情況:射擊方法不能被實現,如果實現裡面具體邏輯為空則毫無意義,即正常情況下不能實現父類的shoot方法,shoot方法必須去掉,從LSP來看如果去掉,則違背了LSP的第一個原則:子類必須實現父類方法。(代碼層面來看如果去掉則會報錯)

package des.lsp;

public class ToyGun extends  AbstractGun {
    @Override
    public void shoot() {
        //此方法不能實現,玩具槍不能射擊
    }
}

解決方法:單獨建立一個抽象類玩具類,把與槍支共有的如聲音、顏色交給抽象槍支類處理,而玩具槍所特有的玩具類的屬性交給抽象玩具類處理,玩具槍類實現玩具抽象類
file

LSP的定義含義2——子類可以含有自己的特性

如圖引入,步槍的實現類即步槍由不同的型號。AUG:狙擊槍可以由望遠鏡功能zoomOut方法。

file
此處Snipper是狙擊手類,狙擊手與狙擊槍是密不可分,屬於組合關係,所以狙擊手類直接使用子類AUG。

package des.lsp;
//狙擊槍
public class AUG extends Rifle {
    //狙擊槍特有功能
    public void zoomOut(){
        System.out.print("通過望遠鏡觀察敵人...");
    }

    @Override
    public void shoot() {
        System.out.print("AUG射擊敵人...");
    }
}
package des.lsp;
//狙擊手
public class Snipper {
    //此處傳入參數為子類,組合關係
    public void killEnemy(AUG aug){
        //觀察
        aug.zoomOut();
        //射擊
        aug.shoot();
    }
}
package des.lsp;

public class Client {
    public static void main(String[] args) {
        
        Snipper s = new Snipper();
        s.killEnemy(new AUG());
    }
}

LSP原則:父類不一定能替換子類

package des.lsp;

public class Client {
    public static void main(String[] args) {
        // write your code here
//        Soldier s = new Soldier();
//        s.setGun(new Rifle());
//        s.killEnemy();

        Snipper s = new Snipper();
        s.killEnemy((AUG) new Rifle());//此處用父類代替了子類
    }
}

報錯代碼
file

LSP的定義含義3——覆蓋或實現父類方法時輸入參數可以被放大

假設有如下場景
父類:方法入參<子類方法入參
file
場景類調用:父類調用自己方法。

package des.lsp;

import java.util.HashMap;

public class Client {
    public static void invoker(){
        Father f = new Father();
        HashMap map = new HashMap();
        f.doSomething(map);
        
    }
    public static void main(String[] args) {

        invoker();
    }
}

輸出結果
file
使用里氏替換原則:把所有父類出現的地方替換為子類

package des.lsp;

import java.util.HashMap;

public class Client {
    public static void invoker(){
        Son f = new Son();
        HashMap map = new HashMap();
        f.doSomething(map);

    }
    public static void main(String[] args) {
emy((AUG) new Rifle());

        invoker();
    }
}

輸出結果
file
我們的本意是調用子類重載的方法,入參為Map的方法,但實際程式執行是調用的從父類繼承的方法。如果子類的方法中入參的範圍大於父類入參的範圍,則子類代替父類的時候,子類的方法永遠不會執行。
從另外角度來看,假如父類入參的範圍大於子類的入參的範圍,則父類替換子類就未必能存在,這時候很可能會調用子類的方法執行。此句話較為抽象,實際情況如下。
file
父類和子類的代碼如下

public class Father {
    public Collection doSomething(Map map){
        System.out.print("父類被執行...");
        return map.values();
    }
}
public class Son extends Father {
    public Collection doSomething(HashMap map) {
       System.out.print("子類執行...");
        return map.values();
    }
}

場景類:調用父類

package des.lsp;

import java.util.HashMap;

public class Client {
    public static void invoker(){
        Father f = new Father();
        HashMap map = new HashMap();
        f.doSomething(map);

    }
    public static void main(String[] args) {
 
        invoker();
    }
}

運行結果:不言而喻,是父類被執行
file
採用LSP後

package des.lsp;

import java.util.HashMap;

public class Client {
    public static void invoker(){
        Son f = new Son();
        HashMap map = new HashMap();
        f.doSomething(map);

    }
    public static void main(String[] args) {

        invoker();
    }
}

file
此時一般人會想,難道不是子類執行嗎?因為子類的入參就是HashMap,肯定要調用這個。
但是此時要考慮一個問題,假如我們的本來意思是就是調用從父類繼承的入參為Map的方法,但是程式執行的時候卻自動為我們執行了子類的方法,此時就會導致混亂。
結論:子類中的方法的輸入參數(前置條件或稱形式參數)必須與父類中的輸入參數一致或者更寬鬆(範圍更大)。

LSP的定義含義4——覆蓋或實現父類的方法時輸出結果可以被縮小

理解:父類的返回類型為T,子類的返回類型為S,即LSP要求S<= T
此時分為兩種情況

  • 如果時覆寫,子類繼承父類,繼承的方法的入參必然相同,此時傳入參數必須時相同或小於,返回的值必然不能大於父類返回值,這是覆寫的要求。
  • 如果時重載,這時候要求子類重載方法的參數類型或數量不相同,其實就是保證輸入參數寬於或等於父類輸入參數,這時候就保證了子類的方法永遠不會被執行,其實就是含義3。

LSP的目的及理解

  • 增強程式的健壯性
  • 保證即使增加子類,原有的子類仍然可以繼續運行。
  • 從一方面來說,在程式中儘量避免直接使用子類的個性,而是通過父類一步一步的使用子類,否則直接使用子類其實就相當於直接把子類當作父類,這就直接導致父類毫無用途,父類和子類的關係也會顯得沒有必要存在了。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • v-bind和v-model的區別 1.v-bind用來綁定數據和屬性以及表達式,縮寫為':' 2.v-model使用在表單中,實現雙向數據綁定的,在表單元素外使用不起作用 v-bind和v-model的區別 什麼是 mvvm? MVVM 是 Model-View-ViewModel 的縮寫。mvv ...
  • 其實這種功能,網上相關的代碼多的是,我也是因為今天正好要用到這個功能,所以臨時寫了下,放這裡保存下,以便將來自己或者別人用的上吧。 當然我寫的是一個hta文件。下麵是完整js代碼,都是調用activex控制項去做事,所以也沒有考慮瀏覽器相容什麼的。 代碼主要分為: 1.選擇目標文件夾 2.遍歷目標文件 ...
  • javascript基礎篇詳情 2019-04-19 簡介 定義 javascript是一門動態弱類型的解釋型編程語言,增強頁面動態效果,實現頁面與用戶之間的實時動態的交互。 javascript是由三部分組成:ECMAScript、DOM、BOM ECMAScript由ECMA-262定義,提供核 ...
  • ...
  • 1.解決 瀏覽器 返回按鈕不刷新的問題 window.onpageshow = function(event) { if (event.persisted) { window.location.reload() }};2.H5 中 JS 禁用安卓手機物理返回鍵 XBack = {}; (functi ...
  • 一、NodeJS簡介 NodeJS是開發伺服器後臺的東西,和PHP、JavaEE、python類似,和傳統的瀏覽器的關註DOM的JS完全不同,將JavaScript觸角伸到了伺服器端。內核是Chrome瀏覽器的V8引擎,解析JavaScript的效率是非常快的。 創始人。 在不升級伺服器配置的情況下 ...
  • Tampermonkey來改造你瀏覽的網頁,獲得更好的上網體驗 ...
  • 一. 標準盒模型和IE盒模型 1. w3c標準盒模型 width和height不包括padding和border 2. ie盒模型 width和height包括padding和border 3. css3中的box-sizing content-box w3c標準盒模型 border-box IE盒 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...