iT邦幫忙

2021 iThome 鐵人賽

DAY 4
2
自我挑戰組

馬克的軟體架構小筆記系列 第 4

30-4 之軟體架構設計原則 3 - LSP 里氏替換原則

軟體架構設計原則一切都是為了下面這兩點,別忘了。

  • 低耦合
  • 高內聚

LSP 這個原則比較傾向是在物件導向才會有的設計原則,這也正常畢竟我們在討論的軟體設計原則 SOLID 最開始為『 物件導向設計原則 』,但是在很多地方事實上也通用,不過這個原則就比較算是在物件導向才能用的。

LSP ( Liskov-Substitution Principle ) 里氏替換原則是什麼呢 ?

根據 《 Clean Architecture 》這本書提到 1988 年,Barbara Liskov 寫下定義子型態的方式 :

若對型態 S 的每一個物件 o1,都存在一個型態為 T 的件 o2,使得在所有針對 T 編寫的程式 P 中,用 o1 替換 o2 後,程式 P 的行為功能不變,則 S 是 T 的子型態

我覺得比較白話文的說法為 :

在使用父類別的地方,如果替換成子類別,則行為功能不變

為什麼會要有這個準則呢 ? 你想想如果替換成子類別不能用,那是不是就有機率這個子類別實際上,不適合長在這個父類別下,因為子類別已經破壞了父類別的繼承體系,就有可能會在未來發生可能的 Bug。

像我在實務上的確有看過,有個子類別已經違反 LSP,但是因為一些原因與時間很難改,結果導致父類別有很多地方,要為了這個子類別寫特殊判斷,結果特殊判斷和原本的行為打架。

不過在 《 Clean Architecture 》 有提到一個重點 :

LSP 視為指導 『 繼承 』的使用,但是多年來已涉及到介面與實作,它已經演變成更廣泛的軟體原則

也就是說這個原則,已經慢慢變成『 除了繼承以外的準則 』

範例

這個範例是 《 Clean Architecture 》中最有名的正方形與長方形問題,正方形只是長方形長與寬相同的情況,所以這裡範例正方形繼承長方形。

然後這個範例違反 LSP 的情況為,如果是用長方形父類別來計算面積答案就對,而如果改成用正方形來計算面積就會錯。別忘了 LSP 的定義為 :

在使用父類別的地方,如果替換成子類別,則行為功能不變

class Rectangle{
    h:number
    w:number
    setHeight(h): void{}
    setWidth(w): void{}
    getArea(){
        return this.w * this.h
    }
}

class Square extends Rectangle{
    setHeight(h): void {
        this.h = h
        this.w = h
    }
    setWidth(w): void {
        this.h = w
        this.w = w
    }
}

const square = new Square()
square.setHeight(10)
square.setWidth(20)

// 這裡就會有問題,行為和答案不合
const area = square.getArea()

小總結

LSP 目前應該是我實務上比較少碰到的,比較大的原因在於比較少用到繼承,主要的原因在於繼承是個依賴性很高的東西,而果真的要用到繼承我也會傾向以『 做什麼 』來設計這個父類別。詳細為什麼不用的原因我覺得這篇文章寫的很清楚了 ~ 可參考

老樣子,來問一下三個問題,來加深記憶

這個知識點可以用來解釋什麼現象

LSP 原則基本上是在討論什麼情況使用繼承會有問題,而事實上很多在研究這個原則時,也慢慢的發現使用繼承的問題。

這個知識點可以和以前的什麼知識連結呢 ?

LSP 讓我連想到以前在討論繼承的可能問題,例如 :

  • 繼承可以減少不少程式碼,但同時也有負牽一髮動全身的問題
  • 容易讓人在父類別加方法,但確發現只有一個子類別用到
  • 繼承有時可能會導致維護性下降,例如看一段子類別有個 workaround,然後往上追可能要到祖父類別後,才知道這個 workaround 是為了解決祖父類別給其它子類別的 workaround 的 workaround。

我要如何運用這個知識點 ?

  • 在開發時傾向用組合或 interface 來處理
  • 如果真的要使用繼承多思考在不同的子類別在使用上會不會衝突

參考資料


上一篇
30-3 之軟體架構設計原則 2 - OCP 開放封閉原則
下一篇
30-5 之軟體架構設計原則 4 - ISP 介面隔離原則
系列文
馬克的軟體架構小筆記29

尚未有邦友留言

立即登入留言