TypeScript 類別(Classes)— 繼承(extends)
簡介
在物件導向程式設計裡,繼承是最核心的概念之一。它讓我們可以把共用的屬性與行為抽取到父類別(base class),子類別(derived class)只需要關注自己的特殊邏輯,從而達到 程式碼重用、降低耦合度、提升可維護性 的效果。
TypeScript 在 ES6 基礎上加入了靜態型別,讓繼承不只是語法層面的「copy‑paste」,而是能在編譯階段即捕捉到型別錯誤,提升開發者的信心。
本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,完整介紹在 TypeScript 中如何使用 extends 進行類別繼承,適合剛入門的朋友以及想深化了解的中級開發者。
核心概念
1. 基本語法與 super 關鍵字
在 TypeScript 中,使用 extends 讓一個類別繼承另一個類別。子類別會自動取得父類別的 public 與 protected 成員,若要呼叫父類別的建構函式或方法,必須使用 super。
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
public move(distance: number): void {
console.log(`${this.name} 移動了 ${distance} 公尺`);
}
}
class Dog extends Animal {
constructor(name: string) {
// 必須先呼叫 super,才能使用 this
super(name);
}
public bark(): void {
console.log('汪汪!');
}
}
const d = new Dog('小白');
d.bark(); // 汪汪!
d.move(10); // 小白 移動了 10 公尺
重點:在子類別的建構函式裡,第一行 必須呼叫
super(...),否則編譯會錯誤。
2. 覆寫(override)與 protected 成員
子類別可以 覆寫 父類別的方法,只要保留相同的簽名即可。若父類別的方法被標記為 protected,則只能在子類別或同一個模組內部呼叫。
class Vehicle {
protected speed: number = 0;
public accelerate(delta: number): void {
this.speed += delta;
console.log(`速度提升至 ${this.speed} km/h`);
}
}
class Car extends Vehicle {
// 覆寫父類別的 accelerate,加入額外的檢查
public accelerate(delta: number): void {
if (delta < 0) {
console.warn('加速值不可為負');
return;
}
super.accelerate(delta); // 呼叫父類別的實作
}
}
const myCar = new Car();
myCar.accelerate(20); // 速度提升至 20 km/h
3. 抽象類別(abstract class)
抽象類別無法直接實例化,只能作為其他類別的基底。它可以定義 抽象方法(沒有實作),強制子類別必須提供實作。
abstract class Shape {
// 抽象方法,子類別必須實作
abstract area(): number;
public describe(): void {
console.log(`此圖形的面積為 ${this.area()}`);
}
}
class Rectangle extends Shape {
constructor(private width: number, private height: number) {
super();
}
public area(): number {
return this.width * this.height;
}
}
const rect = new Rectangle(5, 3);
rect.describe(); // 此圖形的面積為 15
4. 多層繼承與 instanceof 判斷
類別可以形成多層繼承鏈,instanceof 可用來檢測物件在繼承階層中的位置。
class Person {
constructor(public name: string) {}
}
class Employee extends Person {
constructor(name: string, public id: number) {
super(name);
}
}
class Manager extends Employee {
constructor(name: string, id: number, public department: string) {
super(name, id);
}
}
const alice = new Manager('Alice', 101, '研發');
console.log(alice instanceof Manager); // true
console.log(alice instanceof Employee); // true
console.log(alice instanceof Person); // true
5. 使用 static 成員與繼承
static 成員屬於類別本身,子類別會繼承父類別的 static 成員,但不會自動繼承父類別的 實例屬性。
class Counter {
static total = 0;
constructor() {
Counter.total++;
}
static reset(): void {
Counter.total = 0;
}
}
class ClickCounter extends Counter {
// 直接使用父類別的 static 成員
}
new Counter();
new ClickCounter();
new ClickCounter();
console.log(Counter.total); // 3
ClickCounter.reset(); // 重新歸零
console.log(Counter.total); // 0
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記呼叫 super() |
子類別建構函式若未第一行呼叫 super,會編譯錯誤或執行時錯誤。 |
確保 super(...args) 在建構函式的最前面。 |
覆寫時忘記加 override 修飾詞(TS 4.3+) |
若父類別方法被覆寫,未加 override 會失去編譯期檢查。 |
使用 override 關鍵字,讓編譯器保證簽名相符。 |
| 過度使用繼承 | 繼承層級過深會導致維護困難,應優先考慮 組合(composition)或 介面(interface)。 | 只在 “is‑a” 關係明確時才使用繼承。 |
protected 成員被濫用 |
protected 讓子類別直接存取,若濫用會破壞封裝。 |
儘量保持屬性 private,提供必要的 protected 方法。 |
| 靜態成員的繼承混淆 | 靜態成員屬於類別本身,子類別繼承後仍屬於父類別,呼叫時要注意作用域。 | 使用 ClassName.staticMember 明確指向,必要時在子類別重新宣告。 |
最佳實踐
- 使用
abstract強制契約:對於需多個子類別實作的行為,使用抽象類別或介面定義清晰的 API。 - 加上
override:在 TypeScript 4.3 以上,override能防止因簽名變更而產生的隱性錯誤。 - 限制繼承深度:一般建議不超過 3 層,超過時請檢視是否可以改以 組合 或 Mixin。
- 保持建構函式簡潔:讓子類別只傳遞必要參數給
super,避免在子類別中重複父類別的邏輯。 - 利用
readonly:對於不應被子類別修改的屬性,使用readonly加強型別安全。
實際應用場景
| 場景 | 需求 | 繼承解法 |
|---|---|---|
| UI 元件庫 | 多種按鈕(PrimaryButton、DangerButton)共享樣式與事件處理 | 建立 BaseButton 抽象類別,子類別只覆寫顏色或文字。 |
| 遊戲角色系統 | 不同角色(Warrior、Mage)擁有共同的生命值、移動方式,但攻擊方式不同 | Character 為基底,Warrior、Mage 繼承並實作各自的 attack()。 |
| 資料存取層(DAO) | 多種資料來源(MySQL、MongoDB)提供相同的 CRUD 介面 | AbstractRepository<T> 抽象類別定義 create, read 等方法,具體實作在 MySQLRepository、MongoRepository。 |
| 日誌系統 | 基本日誌功能 + 不同環境的額外行為(檔案、遠端) | Logger 基底提供 log(),FileLogger、RemoteLogger 繼承並擴充。 |
總結
繼承 是 TypeScript 類別最具威力的特性之一,它不僅讓程式碼 重用、結構化,更配合靜態型別提供 編譯期安全。本文從基本語法、super、覆寫、抽象類別,到多層繼承與 static 成員,都提供了實作範例與注意事項。
在實務開發中,適度使用繼承、配合介面與組合,才能寫出既易讀又易維護的程式碼。掌握了 extends 的正確用法後,你就能在大型專案中建立清晰的類別層級,讓團隊協作更順暢,未來也能更輕鬆地擴充功能。祝你在 TypeScript 的世界裡,玩得開心、寫得乾淨!