TypeScript | 從 TS 開始學習物件導向 - Interface 用法

神Q超人
Enjoy life enjoy coding
7 min readJun 30, 2019

--

Interface with TypeScript

前言

Hi!大家好,我是神 Q 超人。前言想分享昨晚金曲獎聽見一段很有感觸的話,是 Leo 王説 :「身為一個創作者,我通常想寫什麼就想什麼,我認為,這對於創作者來說,是最重要最重要最重要最重要的事。」,雖然不曉得撥到了自己哪條心弦,但我對於能夠在這個週末的悠閒午後,打下這篇文章的每一字,由衷地感到幸福 😂

Interface 介面

Interface 被稱作介面或是接口,它主要在

與 Class 約定行為,但 Interface 只描述有哪些 Method 和 Property ,不包含怎麼執行。

先簡單舉個例子,在 TypeScript 中宣告變數時,得在宣告時指定型別,例如:

const name: string = '神 Q 超人';

於是 name 就被 string 型別限定了,放進 name 的值就只能是 string 。

而 Interface 就像在限定「 Class 內容」的存在,如上方說的,在 Interface 中,會描述他有哪些 Method 或 Property ,但 Interface 不會也不能實作這些功能,僅僅是要求使用該 Interface 的 Class 得實現它,否則就會發生錯誤,無法正確編譯。

記得上方那段話,接著從程式碼了解 Interface 。

使用方法

在宣告一個 Interface 時,直接使用 interface 做關鍵字宣告,這裡筆者習慣將 Interface 的命名前面都加上一個大寫的 I ,避免和一般的 Class 搞混,以下為一個基本的 Interface :

interface ICar {
name: string;
move(): void;
}

ICar 內部描述了 Property name 和 Method move ,但是並沒有撰寫關於 namemove 是什麼或怎麼執行的程式碼,因為 Interface 只做描述,不做動作。

有了介面後,下方使用 Class 去實作 ICar 所描述的東西:

Class 會用 implements 指定要實作的 Interface ,只要指定了,就一定要把 Interface 內所有的 Method 和 Property 給實作,上方是正確實作的例子,而下方是指定了 Interface 卻未實作出 move 時, TypeScript 會在編譯時給出的錯誤:

Class 沒有替 Interface 指定所有 Method 和 Property 的行為

另外,其實上方的 Class 雖然只撰寫了 Interface 內的行為,但是 Class 還是可以寫下 Interface 內沒有描述到的 Method 和 Property ,

Interface 只是為 Class 約束了最低需要哪些行為而已。

而且,一個 Class 也能夠同時被多個 Interface 給約束,每個 Interface 間使用逗號間隔:

class Car implements ICarA, ICarB {
/**...**/
}

實際運用

現在我們曉得 Interface 的基本功能,但什麼時候會需要它?

假設,今天有個玩具公司出產兩款遙控車,分別為「加速型」和「過彎型」兩種,而它們都使用同一款遙控器控制。

上述的情況可以用 Class 寫成:

上方的程式碼在做的事情很簡單,以下拆為幾個部分講解:

  1. 第 2 行有個 Controller Class ,能夠用它送出 accelerateturn 控制兩種車的行為, Method 都各自接收對應車型的 Class 作為參數。
  2. 第 13 和 16 行為兩種車型的 Class ,除了 name 外,剩下的就是各自主打的功能。
  3. 最後呼叫 Controller 的 Method 分別送入兩種車的實體,執行各自的行為。

是不是很簡單?目前為止都是 Class 的複習,接下來要進行加入 Interface 的情境了。

其實,上面的「加速型」和「過彎型」都只是玩具公司在試水溫,等撈得差不多後,就可以無縫推出同時擁有「速度」及「過彎」優點的「萬能型」,這裡先不討論玩具公司有多無良,只是我們該怎麼修正程式碼呢?

也許該撰寫一個擁有 addSpeedturn 兩個 Method 的「萬能型」 Class ,接著將「萬能型」的實體傳入 Controlleraccelerateturn 執行。

Umm…但是不是哪裡怪怪的?讓我們看回上方例子的第 3 和 7 行,不管是「加速」或「過彎」的 Method ,它們需要的參數都是各自的車型 SpeedCarturnCar Class ,因此「萬能型」的 Class 是無法透過參數傳入 accelerateturn 裡執行。

那要在 Controller 中加入另外兩個「萬能型」專用的 Method 嗎?

也就是說 Controller 會變這樣子:

相信就算我最後說解法真的是這麼做,你們也會不服的對吧?因為 almightAcceleratealmlightyTurn 根本就在做和原本一模一樣的事情,這樣到處增加重複的程式碼,實在是一件很痛苦的事。

因此 Interface 就這麼出現了,比起為參數指定為固定的 Class ,不如以 Interface 代替,這麼一來,只要是實作該 Interface 的 Class ,就可以作為參數傳入,我們也不必擔心那些 Class 會不會缺少某些 Method ,因為 Interface 已經替我們約束過這件事了。

那第一步先宣告 SpeedCarTurnCar 的 Interface ,並以原有的 Class 實作:

接著到 Controller 中,將 accelerateturn 的參數從 Class 改成 Interface :

此時,只要是實作 ISpeedITurn 的 Class 所產生的實體,就能被送入各自的 Method 中執行。

最後只需要再多建立一個同時實作 ISpeedITurnAlmightCar ,就完成了:

結論

在文中的例子裡, AlmightCar 透過實作 ISpeedITurn ,讓它能夠同時執行於傳入 ISpeedITurn 的兩個 Method,因此

適當的使用介面,能夠讓單一類別做多種不同的工作。

而且還有另一點,

如果以特定 Class 的實體作為參數傳入,那對於該 Method 的耦合度就太高了,

因為這麼做等於綁定了只能透過某個 Class 才能執行的情境,但如果是以 Interface 定義的話,不論是哪個 Class ,只要它實作了指定的 Interface 就可以執行該 Method 。

最後用另一個例子來描述 Interface ,假如使用 Interface 訂定了一個游泳的 Method ,那所有不論是「鴨子」、「魚」、「水行俠」等等會游泳的 Class ,都可以用該 Interface 實作,即使他們游泳的方式都不同,但行為是一樣的,

所以 Interface 也是一個抽象的概念

它是游泳,但怎麼遊就是由實作的 Class 內決定的,而且如果少了 Interface ,那在程式變得複雜起來時,你很難知道誰會游泳,除非一個個去看 Class 裡是不是有游泳的 Method ,但也因為沒有 Interface 約束,要是有些人的游泳命名為 swim 有些命名 swimming ,有太多原因都會讓程式變得容易出錯且難以維護。

希望這篇文章能對不了解 Interface 的讀者有所幫助,如果有哪裡講解有問題、不清楚、或需要補充的地方,要再麻煩大家留言告訴我,感激不盡 🙏

最後下禮拜就會開始補上前幾個禮拜讀到的設計模式了 ✌️ ,除了已經講解完 Class 和本篇的 Interface ,需要多多練習它們的使用情境外,如果不快點將學到的設計模式整理成文章內化,我一定會控制不了記憶力將它忘記 😂

參考文章

  1. 深入淺出 C#, 3/e (Head First C#, 3/e)
  2. https://www.typescriptlang.org/docs/handbook/interfaces.html
  3. https://dotblogs.com.tw/initials/2016/06/18/102618

--

--