面向對象設計 里氏替換原則(LSP)

探索神祕未知

主目錄:一個面向對象設計(OOD)的學習思路設計bash

引子:學習

有一隻小麻雀在大平原上,飛呀飛~。飛累了,看見前方一個大鳥... 小麻雀:大鳥兄你好,本鳥叫麻雀!請問您怎麼稱呼? 大鴕鳥:原來是麻雀小弟呀!本鳥叫鴕鳥! 小麻雀:鴕鳥哥耶!小弟飛的累的不行!讓兄弟在您雄偉的身軀上歇歇腳麼? 大鴕鳥:不行!本鳥還走累了呢!那我咋辦? 小麻雀:你飛唄!難道我還拖着你不成? 大鴕鳥:前提是我要是能飛的起來呀! 小麻雀:開什麼玩笑!我們都是鳥,你飛不起來?「飛」是咋們鳥類的特徵,想到飛就想到咋們鳥~。ui


LSP.png

1. 何爲LSP?

  • 全稱:里氏替換原則(Liskov Substitution principle)
  • 定義:派生類(子類)對象可以替換其基類(超類)對象被使用^foot1
  • Barbara Liskov對LSP定義是這麼說的:若對每一個類型S的對象q1,都存在一個類型T的對象q2,使得在全部對T編寫的程序P中,用q1替換q2後,程序P行爲功能不變,則ST的子類型。 聽着有些繞,我將它畫一個類圖便於理解:
    LSP定義理解dsf
在類P中將T的對象q2,換成S的對象q1行爲功能不變
則S繼承T,得如圖所示的關係

2. 何爲L?何爲S?

L:芭芭拉·利斯科夫(Barbara Liskov)由於提出這個原則的女士姓裏 S:替換(Substitution)父類能被子類替換this

  • 替換如上述定義所述,子類替換父類後不會影響其行爲和功能。

3. 爲什麼要有LSP?

①首先談談要是違反LSPspa

  • 來張違反LSP的類圖

違反LSP.png

  • 分析.net

  • 如今我說天上飛着一隻鳥。。。設計

  • 子類麻雀替換父類:天上飛着一隻麻雀。code

  • 子類鴕鳥替換父類:天上飛着一隻鴕鳥。對象

  • 由上由於違反了里氏替代原則,致使整個設計存在嚴重邏輯錯誤。blog

  • 因爲違反了里氏替代原則,間接的違反了OCP原則^foot2。由於明顯能夠看出飛翔對於鴕鳥因該是封閉的。

②再來看一些代碼(LSP的違反致使OCP的違反)

  • 代碼以下

有三個類:鳥、鴕鳥、麻雀。鴕鳥和麻雀都有要去北京的方法

/**
 * 鳥
 */
class Bird{
    public static final int IS_OSTRICH = 1;//是鴕鳥
    public static final int IS_SPARROW = 2;//是麻雀 
    public int isType;
    public Bird(int isType) {
        this.isType = isType;
    }
}
/**
 * 鴕鳥
 */
class Ostrich extends Bird{
    public Ostrich() {
        super(Bird.IS_OSTRICH);
    }
    public void toBeiJing(){
        System.out.print("跑着去北京!");
    }
}

/**
 * 麻雀
 */
class Sparrow extends Bird{
    public Sparrow() {
        super(Bird.IS_SPARROW);
    }
    public void toBeiJing(){
        System.out.print("飛着去北京!");
    }
}

複製代碼

如今有一個方法birdLetGo,統一處理去北京的行爲

public void birdLetGo(Bird bird) {
        if (bird.isType == Bird.IS_OSTRICH) {
            Ostrich ostrich = (Ostrich) bird;
            ostrich.toBeiJing();
        } else if (bird.isType == Bird.IS_SPARROW) {
            Sparrow sparrow = (Sparrow) bird;
            sparrow.toBeiJing();
        }
    }
複製代碼
  • 分析 你們能夠看出,birdLetGo方法明顯的違反了開閉原則^foot2,它必需要知道全部Bird的子類。而且每次建立一個Bird子類就得修改它一次。

③結論

由上面的分析能夠大體的瞭解了遵照LSP的重要性了吧!

  • 若是不遵照,致使邏輯設計缺陷
  • 若是不遵照,致使同時違反開閉原則
  • 單個模型,孤立時並不具備設計意義。當多個模型出現時,抽象提取共同特徵做爲父類(基類),使之任何子類能替代於父類
  • 若是試圖預測全部假設,咱們所獲得的結果可能會充滿不少沒必要要的複雜性。一般最好的辦法是隻預測那些最明顯的LSP的違反狀態,直到設計開始出現脆弱的狀態,纔去處理它們。[^foot3]

4. 基於契約設計能支持LSP?

  • 什麼是契約設計?
  • 經過爲每一個方法聲明的前置條件和後置條件[^foot4]來指定的。要是使一個方法得以執行,前置條件必需要爲真。執行完畢後,該方法要保證後置條件爲真。
  • 一個例子

幾個繼承關係的類

//動物
public class Animal {
    private String food;
    public Animal(String food) {
        this.food = food;
    }
    public String getFood() {
        return food;
    }

}

//鳥
class Bird extends Animal{
    public Bird(String food) {
        super(food);
    }
}

//鴕鳥
class Ostrich extends Bird{
    public Ostrich() {
        super("草");
    }
}

//麻雀
class Sparrow extends Bird{
    public Sparrow() {
        super("蟲子");
    }
}

複製代碼

在動物園對象中調用吃的方法

class Zoo {
    /**
     * 吃早餐
     */
    public String eatBreakfast(Animal animal) {
        return animal.getFood();
    }
}
複製代碼

分析

  • 這裏的知足前置條件就是調用方需知足能接受String這個食物類型
  • 知足後置條件能夠看作是參數和返回類型
  • 前置條件不能更強,只能更弱,好比能夠這樣調用:
Object food = new Zoo().eatBreakfast(new Animal("肉"));
複製代碼
  • 後置條件能夠更強,好比能夠這樣寫:
String food = new Zoo().eatBreakfast(new Ostrich());
複製代碼
  • 這樣咱們就能夠說是前置條件和後置條件就都得以知足

5. 結論總結[^foot3]

  • 若是LSP有效運用,程序會具備更多的可維護性、可重用性和健壯性

  • LSP是使OCP成爲可能的主要原則之一

  • 正是由於子類的可替換性,才使得父類模塊無須修改的狀況就得以擴展

6. 參考文章

[^foot3]: 敏捷軟件開發 第10章 里氏替換原則(LSP) [^foot4]: 前置條件和後置條件是什麼?

相關文章
相關標籤/搜索