六大設(shè)計(jì)原則之里氏替換原則(LSP)

一愈涩、SOLID

設(shè)計(jì)模式的六大原則有:

  • Single Responsibility Principle:單一職責(zé)原則
  • Open Closed Principle:開閉原則
  • Liskov Substitution Principle:里氏替換原則
  • Law of Demeter:迪米特法則
  • Interface Segregation Principle:接口隔離原則
  • Dependence Inversion Principle:依賴倒置原則

把這六個(gè)原則的首字母聯(lián)合起來(兩個(gè) L 算做一個(gè))就是 SOLID (solid痛倚,穩(wěn)定的)舶胀,其代表的含義就是這六個(gè)原則結(jié)合使用的好處:建立穩(wěn)定唤锉、靈活、健壯的設(shè)計(jì)椅贱。下面我們來看一下里氏替換原則懂算。
設(shè)計(jì)模式六大原則(SOLID)

二庇麦、里氏替換原則定義

【所有引用基類的地方必須能透明地使用其子類的對(duì)象】

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

三、里氏替換原則彌補(bǔ)繼承的缺陷

氏替換原則的意思是山橄,所有基類在的地方垮媒,都可以換成子類,程序還可以正常運(yùn)行航棱。這個(gè)原則是與面向?qū)ο笳Z言的繼承特性密切相關(guān)的。

在學(xué)習(xí)java類的繼承時(shí)它抱,我們知道繼承有一些優(yōu)點(diǎn):

  • 子類擁有父類的所有方法和屬性朴艰,從而可以減少創(chuàng)建類的工作量。
  • 提高了代碼的重用性祠墅。
  • 提高了代碼的擴(kuò)展性,子類不但擁有了父類的所有功能撮珠,還可以添加自己的功能金矛。

但有優(yōu)點(diǎn)也同樣存在缺點(diǎn):

  • 繼承是侵入性的。只要繼承驶俊,就必須擁有父類的所有屬性和方法。
  • 降低了代碼的靈活性榕酒。因?yàn)槔^承時(shí),父類會(huì)對(duì)子類有一種約束紊婉。
  • 增強(qiáng)了耦合性辑舷。當(dāng)需要對(duì)父類的代碼進(jìn)行修改時(shí),必須考慮到對(duì)子類產(chǎn)生的影響何缓。

如何揚(yáng)長避短呢?方法是引入里氏替換原則传轰。

四谷婆、里氏替換原則對(duì)繼承進(jìn)行了規(guī)則上的約束

里氏替換原則對(duì)繼承進(jìn)行了規(guī)則上的約束,這種約束主要體現(xiàn)在四個(gè)方面:

  • 子類必須實(shí)現(xiàn)父類的抽象方法股淡,但不得重寫(覆蓋)父類的非抽象(已實(shí)現(xiàn))方法廷区。

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

  • 當(dāng)子類覆蓋或?qū)崿F(xiàn)父類的方法時(shí)隙轻,方法的前置條件(即方法的形參)要比- 父類方法的輸入?yún)?shù)更寬松玖绿。(即只能重載不能重寫)

  • 當(dāng)子類的方法實(shí)現(xiàn)父類的抽象方法時(shí),方法的后置條件(即方法的返回值)要比父類更嚴(yán)格斑匪。

下面對(duì)以上四個(gè)含義進(jìn)行詳細(xì)的闡述

4.1、子類必須實(shí)現(xiàn)父類的抽象方法狡蝶,但不得重寫(覆蓋)父類的非抽象(已實(shí)現(xiàn))方法

在我們做系統(tǒng)設(shè)計(jì)時(shí)贮勃,經(jīng)常會(huì)設(shè)計(jì)接口或抽象類,然后由子類來實(shí)現(xiàn)抽象方法奏瞬,這里使用的其實(shí)就是里氏替換原則。若子類不完全對(duì)父類的方法進(jìn)行實(shí)例化并淋,那么子類就不能被實(shí)例化珍昨,那么這個(gè)接口或抽象類就毫無存在的意義了。

里氏替換原則規(guī)定曼尊,子類不能覆寫父類已實(shí)現(xiàn)的方法脏嚷。父類中已實(shí)現(xiàn)的方法其實(shí)是一種已定好的規(guī)范和契約父叙,如果我們隨意的修改了它,那么可能會(huì)帶來意想不到的錯(cuò)誤趾唱。下面舉例說明一下子類覆寫了父類方法帶來的后果。

public class Father {

    public void fun(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
    }
}

public class Son extends Father {

    @Override
    public void fun(int a, int b) {
        System.out.println(a + "-" + b + "=" + (a - b));
    }
}


public class Client {

    public static void main(String[] args) {
        Father father = new Father();
        father.fun(1, 2);

        // 父類存在的地方夕晓,可以用子類替代
        // 子類Son替代父類Father
        System.out.println("子類替代父類后的運(yùn)行結(jié)果");
                Son son = new Son();
                son.fun(1, 2);
    }
}

運(yùn)行結(jié)果:

1+2=3
子類替代父類后的運(yùn)行結(jié)果
1-2=-1

我們想要的結(jié)果是“1+2=3”悠咱。可以看到析既,方法重寫后結(jié)果就不是了我們想要的結(jié)果了,也就是這個(gè)程序中子類B不能替代父類A拂玻。這違反了里氏替換原則宰译,從而給程序造成了錯(cuò)誤。

我們可以給父類的非抽象(已實(shí)現(xiàn))方法加final修飾熬甚,這樣就在語法層面控制了父類非抽象方法被子類重寫而違反里氏替換原則肋坚。

有時(shí)候父類有多個(gè)子類(Son1肃廓、Son2)诲泌,但在這些子類中有一個(gè)特例(Son2)。要想滿足里氏替換原則哀蘑,又想滿足這個(gè)子類的功能時(shí)葵第,有的伙伴可能會(huì)修改父類(Father)的方法。但是卒密,修改了父類的方法又會(huì)對(duì)其他的子類造成影響哮奇,產(chǎn)生更多的錯(cuò)誤。這是怎么辦呢鼎俘?我們可以為這個(gè)特例(Son2)創(chuàng)建一個(gè)新的父類(Father2),這個(gè)新的父類擁有原父類的部分功能(Father2并不繼承Father,而是持有Father的一個(gè)引用勘天,組合Father,調(diào)用Father里的功能)捉邢,又有不同的功能。這樣既滿足了里氏替換原則巾钉,又滿足了這個(gè)特例的需求秘案。

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

這個(gè)很容易理解赚导,子類繼承了父類赤惊,擁有了父類和方法,同時(shí)還可以定義自己有未舟,而父類沒有的方法掂为。這是在繼承父類方法的基礎(chǔ)上進(jìn)行功能的擴(kuò)展勇哗,符合里氏替換原則寸齐。

4.3 、當(dāng)子類覆蓋或?qū)崿F(xiàn)父類的方法時(shí)渺鹦,方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松

先看一段代碼:

public class Father {
    public void fun(HashMap map){
        System.out.println("父類被執(zhí)行...");
    }
}

public class Son extends Father {
    public void fun(Map map){
        System.out.println("子類被執(zhí)行...");
    }
}

public class Client {

    public static void main(String[] args) {
        System.out.print("父類的運(yùn)行結(jié)果:");
        Father father=new Father();
        HashMap map=new HashMap();
        father.fun(map);
        
        //父類存在的地方毅厚,可以用子類替代
        //子類B替代父類A
        System.out.print("子類替代父類后的運(yùn)行結(jié)果:");
        Son sun=new Son();
        son.fun(map);
    }
}

運(yùn)行結(jié)果:

父類的運(yùn)行結(jié)果:父類被執(zhí)行...
子類替代父類后的運(yùn)行結(jié)果:父類被執(zhí)行...

我們應(yīng)當(dāng)主意,子類并非重寫了父類的方法,而是重載了父類的方法憎茂。因?yàn)樽宇惡透割惖姆椒ǖ妮斎雲(yún)?shù)是不同的竖幔。子類方法的參數(shù)Map比父類方法的參數(shù)HashMap的范圍要大,所以當(dāng)參數(shù)輸入為HashMap類型時(shí)拳氢,只會(huì)執(zhí)行父類的方法,不會(huì)執(zhí)行子類的重載方法放接。這符合里氏替換原則留特。

但如果我將子類方法的參數(shù)范圍縮小會(huì)怎樣?看代碼:

public class Father {
    public void fun(Map map){
        System.out.println("父類被執(zhí)行...");
    }
}

public class Son extends Father {
    public void fun(HashMap map){
        System.out.println("子類被執(zhí)行...");
    }
}


public class Client {

    public static void main(String[] args) {
        System.out.print("父類的運(yùn)行結(jié)果:");
        Father father=new Father();
        HashMap map=new HashMap();
        father.fun(map);
        
        //父類存在的地方苟蹈,可以用子類替代
        //子類B替代父類A
        System.out.print("子類替代父類后的運(yùn)行結(jié)果:");
        Son son=new Son();
        son.fun(map);
    }
}

運(yùn)行結(jié)果:

父類的運(yùn)行結(jié)果:父類被執(zhí)行...
子類替代父類后的運(yùn)行結(jié)果:子類被執(zhí)行...

在父類方法沒有被重寫的情況下右核,子方法被執(zhí)行了,這樣就引起了程序邏輯的混亂菱鸥。所以子類中方法的前置條件必須與父類中被覆寫的方法的前置條件相同或者更寬松。

4.4针炉、當(dāng)子類的方法實(shí)現(xiàn)父類的(抽象)方法時(shí)扳抽,方法的后置條件(即方法的返回值)要比父類更嚴(yán)格
public abstract class Father {
    public abstract Map fun();
}

public class Son extends Father {
    @Override
    public HashMap fun() {
        System.out.println("子類被執(zhí)行...");
        return null;
    }
}

public class Client {

    public static void main(String[] args) {
        Father father=new Son();
        father.fun();
    }
}

運(yùn)行結(jié)果:

子類被執(zhí)行...

注意:是實(shí)現(xiàn)父類的抽象方法,而不是父類的非抽象(已實(shí)現(xiàn))方法镰烧,不然就違法了第一條楞陷。

若在繼承時(shí),子類的方法返回值類型范圍比父類的方法返回值類型范圍大结执,在子類重寫該方法時(shí)編譯器會(huì)報(bào)錯(cuò)艾凯。(Java語法)

參考:

最后給大家送波福利

阿里云折扣快速入口

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末趾诗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子恃泪,更是在濱河造成了極大的恐慌贝乎,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒙具,死亡現(xiàn)場離奇詭異朽肥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)衡招,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來空执,“玉大人穗椅,你說我怎么就攤上這事∑ケ恚” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵默蚌,是天一觀的道長苇羡。 經(jīng)常有香客問我设江,道長,這世上最難降的妖魔是什么叉存? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任鹉胖,我火速辦了婚禮够傍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寂诱。我一直安慰自己安聘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布丘喻。 她就那樣靜靜地躺著念颈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跺撼,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天歉井,我揣著相機(jī)與錄音哈误,去河邊找鬼。 笑死黑滴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的菜谣。 我是一名探鬼主播晚缩,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荞彼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸣皂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤癌压,失蹤者是張志新(化名)和其女友劉穎荆陆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帜消,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泡挺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年命浴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稚新。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖飞醉,靈堂內(nèi)的尸體忽然破棺而出屯阀,到底是詐尸還是另有隱情,我是刑警寧澤钦无,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布盖袭,位于F島的核電站,受9級(jí)特大地震影響弟塞,放射性物質(zhì)發(fā)生泄漏拙已。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一系宫、第九天 我趴在偏房一處隱蔽的房頂上張望建车。 院中可真熱鬧,春花似錦癞志、人聲如沸框产。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膊存。三九已至,卻和暖如春隔崎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爵卒。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工钓株, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人创坞。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓受葛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親携栋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咳秉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容