OOP 15 - Interface Segregation Principles


Posted by tsungtingdu on 2021-09-30

最後,我們來到了 SOLID 當中的介面隔離原則。這裡我們先舉先前提到過的 BaseballPlayer and TennisPlayer 的例子。

由於兩個類別的運動選手,同樣都會擊球和跑奔跑,因此我們將這兩個方法抽象出來,變成一個 Athlete 介面

interface Athlete {
  hit(): void;
  run(): void;
}

接著,我們讓 BaseballPlayer and TennisPlayer 分別去執行這個介面。當然兩者各自有自己的實作方式

class BaseballPlayer implements Athlete {
  name: string

  constructor(name){
    this.name = name
  }

  hit() {
    console.log(`${this.name} can hit baseball`)
  }

  run() {
    console.log(`${this.name} can run`)
  }
}

class TennisPlayer implements Athlete {
  name: string

  constructor(name){
    this.name = name
  }

  hit() {
    console.log(`${this.name} can hit tennis`)
  }

  run() {
    console.log(`${this.name} can run`)
  }
}

最後,我們可以得到

const jeter = new BaseballPalyer('jeter')
const federer = new TennisPlayer('federer')

jeter.hit()    // jeter can hit baseball
jeter.run()    // jeter can run
federer.hit()  // federer can hit tennis
federer.run()  // federer can run

持續為使用者提供新功能

後來,BaseballPlayer 認為他自己除了會打會跑之外,還要很會投球,因此希望 Athlete 可以加入 pitch 這個方法。

interface Athlete {
  hit(): void;
  run(): void;
  pitch(): void;
}

於是 Athlete 從善如流

class BaseballPlayer implements Athlete {
  name: string

  constructor(name){
    this.name = name
  }

  hit() {
    console.log(`${this.name} can hit baseball`)
  }

  run() {
    console.log(`${this.name} can run`)
  }

  pitch() {
    console.log(`${this.name} can pitch`)
  }
}

BaseballPlayer 也順利實作出 pitch 方法

const jeter = new BaseballPalyer('jeter')

jeter.pitch()    // jeter can pitch

問題

不過這時候 TennisPlayer 就覺得有點奇怪,因為他認為網球選手不需要會投球。但是因為同樣執行了 Athlete,因此需要實作 pitch 方法,所以該怎麼辦呢?

於是 TennisPlayer 只好硬著頭皮這樣實作

class TennisPlayer implements Athlete {
  name: string

  constructor(name){
    this.name = name
  }

  hit() {
    console.log(`${this.name} can hit tennis`)
  }

  run() {
    console.log(`${this.name} can run`)
  }

  pitch() {
    console.log(`Sorry ${this.name} cannot pitch`)
  }
}

然後告訴大家其實網球選手不會投球

const federer = new TennisPlayer('jeter')
federer.pitch()    // Sorry federer cannot pitch

不要強迫使用不需要的功能

這時候,介面隔離原則就說話了:

In the field of software engineering, the interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.

使用者不應該被強迫依賴不需要的功能。

回到剛剛的例子,就是不應該強迫 TennisPlayer 去執行 Athlete 的 pitch 功能。

但是BaseballPlayer還是需要 pitch 這個功能啊,該怎麼辦呢?其實很簡單,就把所介面給拆開吧!

interface Athlete {
  hit(): void;
  run(): void;
}

interface Pitch {
  pitch(): void;
}

然後各自執行自己需要的介面。像是 BaseballPlayer 就執行 AthletePitch

class BaseballPlayer implements Athlete, Pitch {
  name: string

  constructor(name){
    this.name = name
  }

  hit() {
    console.log(`${this.name} can hit baseball`)
  }

  run() {
    console.log(`${this.name} can run`)
  }

  pitch() {
    console.log(`${this.name} can pitch`)
  }
}

TennisPlayer 只要執行 Athlete 就行

class TennisPlayer implements Athlete {
  name: string

  constructor(name){
    this.name = name
  }

  hit() {
    console.log(`${this.name} can hit tennis`)
  }

  run() {
    console.log(`${this.name} can run`)
  }
}

各取所需,打完收工!

小結

「介面隔離原則」同樣從使用者的角度出發,不要依賴於完全不需要的功能,以避免未來收到不相關的功能影響。


鐵人賽發表網址:幫自己搞懂物件導向和設計模式


#OOP #Object-oriented programming #SOLID #TypeScript #2021-ironman







Related Posts

[極短篇] XMLHttpRequest

[極短篇] XMLHttpRequest

給工程師的 Sketch Prototyping 簡易入門教學

給工程師的 Sketch Prototyping 簡易入門教學

展開運算子(Spread Operator) & 其餘運算子(Rest Operator)

展開運算子(Spread Operator) & 其餘運算子(Rest Operator)


Comments