最後,我們來到了 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 BaseballPlayer('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
就執行 Athlete
和 Pitch
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`)
}
}
各取所需,打完收工!
「介面隔離原則」同樣從使用者的角度出發,不要依賴於完全不需要的功能,以避免未來收到不相關的功能影響。