TypeScript 介面 (Interface)

在「物件型別」那一章我們提過 interface 可以用來描述物件的形狀。但在物件導向程式設計 (OOP) 中,Interface 有更深層的意義:契約 (Contract)

什麼是「契約」?

想像你是一個老闆,你要請一個「司機」。你不care他是男生女生、高矮胖瘦,你只care他有沒有駕照會不會開車

這裡的「司機規格書」就是 Interface。

interface Driver {
  licenseId: string;
  drive(): void;
}

這個 Interface 規定了:任何想當 Driver 的人,都必須擁有 licenseId 並且 drive()

類別實作 (Class Implements)

類別可以使用 implements 關鍵字來簽署這份契約。

// ✅ 計程車司機:符合契約
class TaxiDriver implements Driver {
  licenseId = 'A123456789';

  drive() {
    console.log('計程車出發!');
  }

  // 他可以有額外的技能,這不違反契約
  collectFare() {
    console.log('收錢');
  }
}

// ❌ 賽車手:違約!(沒有 licenseId)
class Racer implements Driver {
  // Error: Property 'licenseId' is missing in type 'Racer'
  drive() {
    console.log('飆車!');
  }
}

這就是 Interface 強大的地方:它保證了行為的一致性。你的系統某個部分需要一個 Driver,你可以放心地把 TaxiDriver 丟進去,因為 TS 保證他一定會 drive()

介面繼承 (Interface Extends)

Interface 也可以繼承其他的 Interface。這就像是契約的「增補條款」。

interface Animal {
  eat(): void;
}

// Dog 繼承了 Animal 的所有規定,並且多了 bark
interface Dog extends Animal {
  bark(): void;
}

const myDog: Dog = {
  eat() {
    console.log('Nom nom');
  },
  bark() {
    console.log('Woof!');
  },
};

implements vs extends

這是最容易搞混的:

  • extends (繼承):是「這那種東西」。(例如:狗動物)。子類別會繼承父類別的程式碼。
  • implements (實作):是「這能做那件事」。(例如:手機拍照)。類別不會繼承 Interface 的程式碼,只是被檢查「有沒有做這些事」。

一個類別只能 extends 一個父類別,但可以 implements 多個 Interface。

class SmartPhone extends Electronics implements Camera, MusicPlayer, Phone {
  // ... 必須實作拍照、播音樂、打電話的所有功能
}

宣告合併 (Declaration Merging)

這是 Interface 獨有的黑魔法。如果你定義了兩次同名的 Interface,TS 會自動把它們「合體」。

interface Box {
  height: number;
}

interface Box {
  width: number;
}

// 現在 Box 同時有 height 和 width
const b: Box = { height: 10, width: 20 };

這在擴充第三方套件的型別時非常有用(例如幫 Window 物件加屬性)。type 則做不到這一點,會報錯「重複宣告」。