TypeScript 抽象類別 (Abstract Class)

抽象類別 (Abstract Class) 是不能直接實例化的類別,它作為其他類別的基礎類別,定義了子類別必須實作的方法和屬性。

基本語法

使用 abstract 關鍵字定義抽象類別和抽象成員:

abstract class Shape {
  // 抽象方法 - 沒有實作,子類別必須實作
  abstract getArea(): number;
  abstract getPerimeter(): number;

  // 一般方法 - 有實作,子類別可以直接使用
  describe(): string {
    return `面積: ${this.getArea()}, 周長: ${this.getPerimeter()}`;
  }
}

// 無法直接實例化
// const shape = new Shape();  // 錯誤:無法建立抽象類別的實例

實作抽象類別

abstract class Shape {
  abstract getArea(): number;
  abstract getPerimeter(): number;

  describe(): string {
    return `面積: ${this.getArea()}, 周長: ${this.getPerimeter()}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }

  getArea(): number {
    return Math.PI * this.radius ** 2;
  }

  getPerimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}

class Rectangle extends Shape {
  constructor(
    private width: number,
    private height: number
  ) {
    super();
  }

  getArea(): number {
    return this.width * this.height;
  }

  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }
}

const circle = new Circle(5);
console.log(circle.describe()); // 面積: 78.54, 周長: 31.42

const rectangle = new Rectangle(4, 6);
console.log(rectangle.describe()); // 面積: 24, 周長: 20

抽象屬性

抽象類別也可以定義抽象屬性:

abstract class Animal {
  abstract name: string;
  abstract readonly species: string;

  abstract speak(): void;

  introduce(): void {
    console.log(`我是 ${this.name},屬於 ${this.species}`);
  }
}

class Dog extends Animal {
  name: string;
  readonly species: string = '犬科';

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

  speak(): void {
    console.log(`${this.name} 說:汪汪!`);
  }
}

const dog = new Dog('小黑');
dog.introduce(); // 我是小黑,屬於犬科
dog.speak(); // 小黑 說:汪汪!

抽象類別與介面的差異

特性抽象類別介面
實作可以有實作不能有實作
建構子可以有不能有
存取修飾子支援不支援 (全部是 public)
屬性初始值可以有不能有
繼承數量只能繼承一個可以實作多個
用途定義基本行為和共用邏輯定義契約/規格
// 介面 - 純粹的契約
interface Printable {
  print(): void;
}

// 抽象類別 - 包含共用邏輯
abstract class Document {
  constructor(protected title: string) {}

  abstract getContent(): string;

  print(): void {
    console.log(`=== ${this.title} ===`);
    console.log(this.getContent());
  }
}

class Report extends Document implements Printable {
  constructor(
    title: string,
    private data: string[]
  ) {
    super(title);
  }

  getContent(): string {
    return this.data.join('\n');
  }
}

結合抽象類別和介面

interface Serializable {
  serialize(): string;
}

interface Deserializable {
  deserialize(data: string): void;
}

abstract class Entity implements Serializable {
  abstract id: number;
  abstract name: string;

  serialize(): string {
    return JSON.stringify({ id: this.id, name: this.name });
  }

  abstract validate(): boolean;
}

class User extends Entity implements Deserializable {
  id: number = 0;
  name: string = '';
  email: string = '';

  validate(): boolean {
    return this.name.length > 0 && this.email.includes('@');
  }

  deserialize(data: string): void {
    const parsed = JSON.parse(data);
    this.id = parsed.id;
    this.name = parsed.name;
    this.email = parsed.email;
  }
}

抽象類別的繼承鏈

abstract class Vehicle {
  abstract brand: string;
  abstract start(): void;

  honk(): void {
    console.log('Beep!');
  }
}

// 仍然是抽象類別,因為沒有實作所有抽象成員
abstract class Car extends Vehicle {
  wheels: number = 4;

  abstract drive(): void;
}

// 具體類別,實作了所有抽象成員
class Sedan extends Car {
  brand: string;

  constructor(brand: string) {
    super();
    this.brand = brand;
  }

  start(): void {
    console.log(`${this.brand} 引擎啟動`);
  }

  drive(): void {
    console.log(`${this.brand} 轎車行駛中`);
  }
}

const sedan = new Sedan('Toyota');
sedan.start(); // Toyota 引擎啟動
sedan.drive(); // Toyota 轎車行駛中
sedan.honk(); // Beep!

實用範例

樣板方法模式 (Template Method Pattern)

abstract class DataParser {
  // 樣板方法 - 定義演算法的骨架
  parse(data: string): any {
    const validated = this.validate(data);
    if (!validated) {
      throw new Error('Invalid data');
    }
    const parsed = this.parseData(data);
    return this.transform(parsed);
  }

  // 抽象方法 - 子類別必須實作
  protected abstract validate(data: string): boolean;
  protected abstract parseData(data: string): any;

  // 鉤子方法 - 可選覆寫
  protected transform(data: any): any {
    return data;
  }
}

class JSONParser extends DataParser {
  protected validate(data: string): boolean {
    try {
      JSON.parse(data);
      return true;
    } catch {
      return false;
    }
  }

  protected parseData(data: string): any {
    return JSON.parse(data);
  }
}

class CSVParser extends DataParser {
  protected validate(data: string): boolean {
    return data.includes(',');
  }

  protected parseData(data: string): any {
    return data.split('\n').map((line) => line.split(','));
  }

  protected transform(data: any[][]): any {
    const headers = data[0];
    return data.slice(1).map((row) => {
      const obj: any = {};
      headers.forEach((header, index) => {
        obj[header] = row[index];
      });
      return obj;
    });
  }
}

策略模式基底

abstract class PaymentStrategy {
  abstract pay(amount: number): boolean;

  protected formatAmount(amount: number): string {
    return `$${amount.toFixed(2)}`;
  }

  getDescription(): string {
    return 'Payment Strategy';
  }
}

class CreditCardPayment extends PaymentStrategy {
  constructor(private cardNumber: string) {
    super();
  }

  pay(amount: number): boolean {
    console.log(`信用卡支付 ${this.formatAmount(amount)}`);
    return true;
  }

  getDescription(): string {
    return `Credit Card ending in ${this.cardNumber.slice(-4)}`;
  }
}

class PayPalPayment extends PaymentStrategy {
  constructor(private email: string) {
    super();
  }

  pay(amount: number): boolean {
    console.log(`PayPal (${this.email}) 支付 ${this.formatAmount(amount)}`);
    return true;
  }

  getDescription(): string {
    return `PayPal: ${this.email}`;
  }
}

// 使用
class ShoppingCart {
  constructor(private paymentStrategy: PaymentStrategy) {}

  checkout(total: number): void {
    console.log(`使用 ${this.paymentStrategy.getDescription()}`);
    this.paymentStrategy.pay(total);
  }
}

Repository 模式

abstract class Repository<T> {
  protected items: Map<string, T> = new Map();

  abstract getKey(item: T): string;

  save(item: T): void {
    const key = this.getKey(item);
    this.items.set(key, item);
  }

  findById(id: string): T | undefined {
    return this.items.get(id);
  }

  findAll(): T[] {
    return Array.from(this.items.values());
  }

  delete(id: string): boolean {
    return this.items.delete(id);
  }
}

interface User {
  id: string;
  name: string;
  email: string;
}

class UserRepository extends Repository<User> {
  getKey(user: User): string {
    return user.id;
  }

  findByEmail(email: string): User | undefined {
    return this.findAll().find((user) => user.email === email);
  }
}

const userRepo = new UserRepository();
userRepo.save({ id: '1', name: '小明', email: 'ming@example.com' });
console.log(userRepo.findByEmail('ming@example.com'));

何時使用抽象類別

  1. 共用實作邏輯:當多個類別有共用的方法實作時
  2. 定義部分契約:當你想定義一些方法由子類別實作,同時提供一些預設行為
  3. 建立繼承層次:當類別之間有 "is-a" 關係時
  4. 樣板方法:當你想定義演算法的骨架,讓子類別填入細節