TypeScript 抽象类

161 阅读4分钟

理解抽象类的本质

在 TypeScript 中,抽象类(Abstract Classes)是面向对象编程的核心概念之一,它充当着类的蓝图设计的契约。抽象类定义了派生类必须实现的结构规范,同时提供可重用的公共实现,完美平衡了接口的严格性类的具体性

classDiagram
    class AbstractClass {
        <<abstract>>
        abstract abstractMethod()
        concreteMethod()
        private privateField
        protected protectedMethod()
    }
    
    class ConcreteClass {
        abstractMethod()
        additionalMethod()
    }
    
    AbstractClass <|-- ConcreteClass : 继承

抽象类的核心特性

1. 无法直接实例化

抽象类的主要目的是作为基类被扩展,不能直接创建实例:

abstract class Animal {
    abstract makeSound(): void;
}

// 错误!无法创建抽象类的实例
const animal = new Animal(); 

2. 包含抽象方法

抽象方法只有声明没有实现,强制派生类提供具体实现:

abstract class PaymentProcessor {
    abstract processPayment(amount: number): boolean;
}

class CreditCardProcessor extends PaymentProcessor {
    processPayment(amount: number) {
        console.log(`Processing credit card payment: $${amount}`);
        return true;
    }
}

3. 支持具体实现

抽象类可以包含完全实现的方法和属性:

abstract class Shape {
    // 抽象方法
    abstract calculateArea(): number;
    
    // 具体实现的方法
    describe(): string {
        return `This shape has an area of ${this.calculateArea()}`;
    }
}

4. 访问修饰符支持

抽象类支持完整的访问控制:

abstract class DatabaseConnector {
    protected abstract connect(): void;
    public abstract query(sql: string): any[];
    private logConnection() {
        console.log("Connection established");
    }
}

抽象类 vs 接口:何时使用?

特性抽象类接口
实现细节可提供方法实现只有方法签名
构造函数支持不支持
访问修饰符支持 public/protected/private所有成员都 public
多继承一个类只能继承一个抽象类一个类可实现多个接口
属性状态可包含实例属性只能包含只读属性
初始值设定可在构造函数中初始化不支持

选择原则

  • 需要共享代码实现时 → 使用抽象类
  • 需要多继承能力时 → 使用接口
  • 需要定义对象形状时 → 使用接口
  • 需要构造函数逻辑时 → 使用抽象类

抽象类实战

1. UI 组件框架设计

abstract class UIComponent {
    constructor(protected element: HTMLElement) {}
    
    // 抽象方法
    abstract render(): void;
    abstract onEvent(eventType: string): void;
    
    // 公共实现
    protected log(message: string): void {
        console.log(`[${this.constructor.name}] ${message}`);
    }
    
    // 模板方法模式
    public init(): void {
        this.render();
        this.addEventListeners();
        this.log("Component initialized");
    }
    
    protected addEventListeners(): void {
        // 基础事件监听逻辑
    }
}

class ButtonComponent extends UIComponent {
    render() {
        this.element.innerHTML = `<button class="btn">Click Me</button>`;
    }

    onEvent(eventType: string) {
        if (eventType === "click") {
            this.element.querySelector('button')?.addEventListener('click', () => {
                alert("Button clicked!");
            });
        }
    }
}

2. 数据访问层抽象

abstract class DataRepository<T> {
    protected abstract tableName: string;
    
    // 抽象CRUD操作
    abstract getById(id: number): Promise<T>;
    abstract create(item: T): Promise<void>;
    abstract update(id: number, item: Partial<T>): Promise<void>;
    abstract delete(id: number): Promise<void>;
    
    // 公共实现
    protected logOperation(operation: string): void {
        console.log(`Operation ${operation} on table ${this.tableName}`);
    }
    
    public async transaction(operations: Function[]): Promise<void> {
        this.logOperation("transaction start");
        try {
            for (const op of operations) {
                await op();
            }
        } catch (error) {
            console.error("Transaction failed:", error);
        }
        this.logOperation("transaction end");
    }
}

class UserRepository extends DataRepository<User> {
    protected tableName = "users";
    
    async getById(id: number) {
        this.logOperation("getById");
        // 实际数据库查询逻辑...
        return mockDatabase.users[id];
    }
    
    // 其他方法的实现...
}

3. 游戏实体系统

abstract class GameEntity {
    constructor(
        public x: number,
        public y: number,
        protected health: number
    ) {}
    
    // 抽象方法
    abstract update(deltaTime: number): void;
    abstract render(ctx: CanvasRenderingContext2D): void;
    
    // 公共方法
    move(dx: number, dy: number): void {
        this.x += dx;
        this.y += dy;
    }
    
    takeDamage(amount: number): void {
        this.health -= amount;
        if (this.health <= 0) {
            this.onDeath();
        }
    }
    
    protected onDeath(): void {
        console.log(`${this.constructor.name} destroyed!`);
    }
}

class Player extends GameEntity {
    constructor(x: number, y: number) {
        super(x, y, 100);
    }
    
    update() {
        // 玩家特定更新逻辑
    }
    
    render(ctx) {
        ctx.fillStyle = "blue";
        ctx.fillRect(this.x, this.y, 50, 50);
    }
}

class Enemy extends GameEntity {
    update() {
        // AI移动逻辑
        this.move(1, 0);
    }
    
    render(ctx) {
        ctx.fillStyle = "red";
        ctx.beginPath();
        ctx.arc(this.x, this.y, 25, 0, Math.PI * 2);
        ctx.fill();
    }
}

高级模式

1. 模板方法模式

抽象类定义算法骨架,具体步骤由子类实现:

abstract class DataExporter {
    // 模板方法
    public export(): void {
        this.validateData();
        this.transformData();
        this.save();
        this.cleanup();
    }
    
    protected abstract transformData(): void;
    protected abstract save(): void;
    
    // 可选的钩子方法
    protected validateData(): void {
        // 默认验证逻辑
    }
    
    protected cleanup(): void {
        console.log("Cleaning up resources");
    }
}

class CSVExporter extends DataExporter {
    protected transformData() {
        console.log("Converting data to CSV format");
    }
    
    protected save() {
        console.log("Saving CSV file to disk");
    }
}

class JSONExporter extends DataExporter {
    protected transformData() {
        console.log("Converting data to JSON format");
    }
    
    protected save() {
        console.log("Uploading JSON to cloud storage");
    }
    
    // 覆盖钩子方法
    protected validateData() {
        console.log("Performing JSON-specific validation");
        super.validateData();
    }
}

2. 抽象属性与构造函数

abstract class Device {
    abstract readonly id: string;
    protected abstract connectionStatus: boolean;
    
    constructor(public name: string) {}
    
    abstract connect(): void;
    abstract disconnect(): void;
    
    get status(): string {
        return this.connectionStatus ? "Connected" : "Disconnected";
    }
}

class Phone extends Device {
    readonly id = "PHN-12345";
    protected connectionStatus = false;
    
    connect() {
        console.log("Connecting phone to network...");
        this.connectionStatus = true;
    }
    
    disconnect() {
        console.log("Disconnecting phone...");
        this.connectionStatus = false;
    }
}

3. 多层抽象体系

// 一级抽象
abstract class Sensor {
    abstract readValue(): number;
}

// 二级抽象
abstract class TemperatureSensor extends Sensor {
    abstract get unit(): 'C' | 'F';
    
    display(): string {
        return `${this.readValue()}°${this.unit}`;
    }
}

// 具体实现
class DigitalThermometer extends TemperatureSensor {
    get unit() { return 'C'; }
    
    readValue() {
        // 实际传感器读数逻辑
        return 22.5;
    }
}

class AnalogThermometer extends TemperatureSensor {
    get unit() { return 'F'; }
    
    readValue() {
        // 传感器读数逻辑
        return 72.3;
    }
}

抽象类的限制与最佳实践

注意事项:

  1. 单继承限制:TypeScript 不支持多重继承(不能同时继承多个抽象类)
  2. 构造函数陷阱:抽象类仍可定义构造函数,但无法直接调用 new AbstractClass()
  3. 抽象属性:抽象属性必须在派生类中实现
  4. 混合类问题:抽象类不能直接与混入(mixins)结合使用

最佳实践:

// 1. 为抽象方法提供清晰文档
abstract class Validator {
    /**
     * Validate input data according to business rules
     * @param data Input to validate
     * @returns Error message if invalid, null if valid
     */
    abstract validate(data: any): string | null;
}

// 2. 合理使用访问修饰符
abstract class ApiClient {
    // 子类可访问的受保护方法
    protected async request(endpoint: string) {
        // 公共请求逻辑
    }
}

// 3. 结合接口确保实现完整性
interface Loggable {
    log(message: string): void;
}

abstract class BaseService implements Loggable {
    abstract log(message: string): void; // 强制实现接口方法
    abstract execute(): void;
}

// 4. 避免过度抽象:只在必要时使用
class SimpleUtil {
    // 不需要抽象时使用具体类
    static formatDate(date: Date) {
        return date.toISOString();
    }
}

抽象类的未来:ECMAScript 提案

虽然 TypeScript 的抽象类当前是其类型系统的专属特性,但 ECMAScript 标准中已有正式加入抽象类的提案:

// ECMAScript 提案中的原生抽象类
abstract class NativeAbstract {
    abstract method(): void;
}

class NativeConcrete extends NativeAbstract {
    method() {
        console.log("Native abstract class implementation");
    }
}

当前状态

抽象类的价值

抽象类在 TypeScript 中扮演着至关重要的角色,特别适合以下场景:

  • 框架开发:定义核心结构同时提供部分实现
  • 复杂系统:建立清晰的层次关系和契约
  • 团队协作:规范实现标准,减少错误
  • 代码复用:集中公共逻辑减少重复代码
graph TB
    A[抽象类] --> B[定义框架结构]
    A --> C[强制实现契约]
    A --> D[提供公共实现]
    A --> E[控制访问权限]
    B --> F[提高开发效率]
    C --> G[减少错误]
    D --> H[减少重复代码]
    E --> I[更好封装]

当您需要在严格接口代码重用之间找到平衡点时,抽象类是最佳选择。