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'));
何時使用抽象類別
- 共用實作邏輯:當多個類別有共用的方法實作時
- 定義部分契約:當你想定義一些方法由子類別實作,同時提供一些預設行為
- 建立繼承層次:當類別之間有 "is-a" 關係時
- 樣板方法:當你想定義演算法的骨架,讓子類別填入細節