OOP - 5 抽象類別與介面 (2)


Posted by tsungtingdu on 2021-09-20

什麼是抽象類別

an abstract class, or abstract base class (ABC), is a class that cannot be instantiated because it is either labeled as abstract or it simply specifies abstract methods (or virtual methods). An abstract class may provide implementations of some methods, and may also specify virtual methods via signatures that are to be implemented by direct or indirect descendants of the abstract class.

抽象類別是一種無法建立實例的類別。抽象類別可以提供方法(可以有、或沒有實作細節)給 child 類別使用,另一方面,child 類別需要能夠實現所繼承的抽象類別的所有方法。

在上一篇文章的例子當中,我們並沒有在 Athlete 當中提供 hit 方法的實作細節,但實際上我們也可以提供,像是下面這樣

abstract class Athlete {
  name: string

  constructor(name: string) {
    this.name = name
  }

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

接著,讓 BaseballPlayer 直接沿用 Athletehit 方法,而讓 TennisPlayer 用自已定義的 hit 方法來覆寫原本的 hit 方法:

class BaseballPlayer extends Athlete {
  pitch() {
    console.log(`${this.name} can pitch the ball`)
  }
}

class TennisPlayer extends Athlete {
  hit() {
    console.log(`${this.name} can hit tennis`)
  }
  serve() {
    console.log(`${this.name} can serve`)
  }
}

得到結果

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

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

什麼是介面

an interface is a shared boundary across which two or more separate components of a computer system exchange information.

廣義來說,介面是兩個實體交換資訊的邊界,在這個邊界上,資訊格式會被定義(規範),好讓兩個實體能夠順利交換資訊。

In some object-oriented languages, especially those without full multiple inheritance, the term interface is used to define an abstract type that contains no data but defines behaviours as method signatures.
...
An interface is thus a type definition; anywhere an object can be exchanged (for example, in a function or method call) the type of the object to be exchanged can be defined in terms of one of its implemented interfaces or base-classes rather than specifying the specific class.

而在一些物件導向程式語言當中,介面就像是抽象類別一種,定義了行為但不包含任何資料。
...一個介面因此就是一個型別 (type) 的定義,在任何可以交換物件的地方,要被交換的物件的類型,可以由介面來定義,而不是它的類別 (class)。

上面的解釋其實有點抽象和複雜,舉一個現實生活中的例子的話,我們每天都在使用的「插座的形狀」,其實就是一種介面,它定義了兩個實體 (輸電線的插座、電器的插頭) 互動的格式,但不參與他們實現的方式。實際上實現的是主體是

  • 插座的製造商。他們根據這個形狀,做出同樣形狀的插座
  • 電器的製造商。他們根據這個形狀,做出同樣形狀的插頭

所以有了介面,電器的製造商不需要管誰是插座的製造商,因為不管是誰來生產插座,都會是一樣的形狀。反之亦然。

在物件導向程式設計當中,介面的意義就是,當我們只要定義好一個交換 (或互動) 的格式,那麼我們就可以完全不需要去管究竟是哪個類別的物件要來進行資料交換或互動。

以先前的例子來說,就是有任何的類別執行 (implement) Hit 這個介面的話,就可以百分之百保證這個類別有一個 hit 方法可以呼叫!

class XXXX implements Hit {
  ...
}

關於型別 (type)

另一方面,在 TypeScript 當中,我們可以定義一個方法當中「輸入」和「輸出」的型別 (type)。譬如以下面的例子來說,這裡有一個可以獲得棒球選手報告的 getReport,當我們傳入全壘打的數量,就可以獲得一行報告。

在這個方法當中,我們定義了輸入資料 homeRuns 的型別為數字,輸入資料為字串,但是沒有定義最後輸出的字串長什麼樣子

interface GetReport {
  getHomerunReport(homeRuns: number): string;
}

接著,我們讓 BaseballPlayer 來執行介面 GetReport,並實際定義 getHomerunReport 的細部內容

class BaseballPlayer implements GetReport {
  name: string

  constructor(name: string) {
    this.name = name
  }

  getHomerunReport(homeRuns: number): string {
    return `${this.name} has ${homeRuns} homeruns this season!`
  }
}

最後,我們就可以呼叫 getHomerunReport,傳入一個數字,得到一個字串

const jeter = new BaseballPlayer('jeter')
jeter.getHomerunReport(20)  // jeter hit 20 homeruns this season!

雖然在 TypeScript 當中型別和介面看起來是不一樣的東西,但實際上當我們在定義型別的時候,就是在定義廣義上的介面。

抽象類別和介面的差別

相較於建立一個 parent 類別,抽象類別和介面帶來了更多的彈性,可以在定義一個類別所需要屬性或方法時,也讓類別擁有自己實作的方式。

抽象類別和介面兩者看起來很相似,不過還是有差異,像是

  • 抽象類別本身其實也可以定義實作方式,當一個類別繼承抽象類別之後之後,可以決定是否要繼續採用,或是覆寫成自己的實作方式。而介面是完全無法定義實作方式
  • 一個類別只能繼承一個抽象類別,但是可以執行多個介面,像是
class BaseballPlayer implements Hit, Run, Jump {
  ...
}

使用抽象類別時,它和 child 類別本身還是有「A 是 B 的一種」的關係,以剛剛的例子來說,就是BaseballPlayerAthlete 的一種。因此跟先前提到的 parent 類別一樣,抽象類別和一般類別需要建立在有邏輯的階層關係上,才不會造成未來實作上的混亂和錯誤。

而建立介面的時候,就不太需要在乎和類別之間的關係,只要關心自己需要定義什麼互動的格式就行了!


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


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







Related Posts

使用Pagy製作Blog分頁(Ruby on Rails)

使用Pagy製作Blog分頁(Ruby on Rails)

Day07:V8 bytecode 系列文總結

Day07:V8 bytecode 系列文總結

Express Web App 靜態、動態路由

Express Web App 靜態、動態路由


Comments