SOLID 里氏替換原則

SOLID 里氏替換原則(LSP, Liskov Substitution Principle)

-4 min read

這個原則是指,任何一個可以使用父類別(基礎類別)物件的地方,都應該能夠使用子類別(衍生類別)物件,而不會產生錯誤或不正確的結果。簡單來說,就是子類別要能夠替換掉父類別,而不會造成程式錯誤或行為異常。

  • 先驗條件不可以強化:父類別要求的是矩形,子類別就不能要求得更嚴,只准人家給正方形
  • 後驗條件不可以弱化:父類別產出的是正方形,子類別不能說沒關係啦,就給人家隨便一個矩形
  • 不變條件必須保持不變:父類別是一個產生矩形的方法,子類別不能背骨,跑去產生圓形

子層的實作都應該符合父層的介面設計

生活中的範例

不能認為只要是鳥類都會飛行,這會同是鳥類企鵝尷尬

假設你認為鳥類都有翅膀,且都會飛行,那麼應該各種鳥類應該都會飛行,例如:雞、鴿子、企鵝、鴕鳥, 但是鴕鳥跟企鵝不會飛呀,所以這個設計是不符合里氏替換原則的。

應該將鳥類分為都有翅膀,並且在進一步分類為「會飛的鳥」與「不會飛的鳥」,

JavaScript 範例

錯誤範例

企鵝強制實現了 fly() 方法,但是不符合其特性:

class Bird {  fly() {    console.log('我正在飛行')  }}class Pigeon extends Bird {  // Pigeon 也可以飛行}class Penguin extends Bird {  fly() {    console.log('我正在飛行') // 錯誤的實現,企鵝不能飛行  }}const bird = new Pigeon()bird.fly() // 正常情況下可以飛行const penguin = new Penguin()penguin.fly() // 錯誤的行為,企鵝不應該飛行

正確範例

在這個修改後的程式碼中,Bird 類別有了一個新的特性 hasWings,這個特性表明該類別的所有子類別都有翅膀。另外我們也新增了 FlyingBird 與 NonFlyingBird 這兩個子類別,分別表明該鳥類是否可以飛行。Pigeon 繼承 FlyingBird,因此它可以飛行;而 Penguin 繼承 NonFlyingBird,因此它不能飛行。

也因此符合 Liskov 替換原則,Pigeon 與 Penguin 都可以代替父類別 Bird,因為他們都有翅膀。

class Bird {  constructor() {    this.hasWings = true  }}class FlyingBird extends Bird {  constructor() {    super()    this.canFly = true  }  fly() {    console.log('我正在飛行')  }}class NonFlyingBird extends Bird {  constructor() {    super()    this.canFly = false  }  fly() {    console.log('我不能飛行')  }}class Pigeon extends FlyingBird {  constructor() {    super()  }}class Penguin extends NonFlyingBird {  constructor() {    super()  }}const bird = new Pigeon()console.log(bird.hasWings) // trueconsole.log(bird.canFly) // truebird.fly() // 我正在飛行const penguin = new Penguin()console.log(penguin.hasWings) // trueconsole.log(penguin.canFly) // falsepenguin.fly() // 我不能飛行

參考文章