Agent X-Ray
RuntimeNotesAbout
Notes/代码工程/TypeScript 深度教程/第8章

第8章:类的高级能力 —— 继承、抽象、混入与装饰器

3 分钟 · 更新于 2026-09-01

第8章:类的高级能力 —— 继承、抽象、混入与装饰器

类是 JavaScript 的运行时结构,TypeScript 为它增加静态契约。本章讨论继承、多态、抽象类、访问控制、Mixin 和装饰器,并强调组合优先与运行时边界。


一、继承与多态

ts
abstract class PaymentMethod {
  abstract pay(amount: number): Promise<string>;
}

class CardPayment extends PaymentMethod {
  async pay(amount: number): Promise<string> {
    return `card:${amount}`;
  }
}

class WalletPayment extends PaymentMethod {
  async pay(amount: number): Promise<string> {
    return `wallet:${amount}`;
  }
}
ts
async function checkout(method: PaymentMethod, amount: number): Promise<string> {
  return method.pay(amount);
}

调用者依赖抽象契约,具体实例在运行时多态分发。

二、抽象类与接口

抽象类可以包含:

  • 实例状态。
  • 已实现方法。
  • 构造逻辑。
  • 受保护成员。
  • 抽象方法。

接口主要描述形状,可以被多个无继承关系的类实现。

选择建议:

  • 只表达能力契约:接口。
  • 需要共享实现和受控扩展点:抽象类。
  • 需要组合多个能力:接口更灵活,因为类只能继承一个基类。

三、访问修饰符

ts
class Session {
  public readonly id: string;
  protected expiresAt: Date;
  private token: string;

  constructor(id: string, token: string, expiresAt: Date) {
    this.id = id;
    this.token = token;
    this.expiresAt = expiresAt;
  }
}

TypeScript private 主要由类型系统检查。若必须在运行时阻止外部访问,使用 JavaScript #token

ts
class SecureSession {
  #token: string;

  constructor(token: string) {
    this.#token = token;
  }
}

四、protected 的克制使用

protected 让子类依赖父类内部状态,容易形成脆弱基类。优先暴露小型受保护方法,而不是大量字段:

ts
abstract class Job {
  async run(): Promise<void> {
    await this.beforeRun();
    await this.execute();
    await this.afterRun();
  }

  protected async beforeRun(): Promise<void> {}
  protected abstract execute(): Promise<void>;
  protected async afterRun(): Promise<void> {}
}

这是一种模板方法模式,但扩展点必须明确且稳定。

五、组合优先于继承

ts
interface Logger {
  log(message: string): void;
}

interface Repository<T> {
  save(value: T): Promise<void>;
}

class UserService {
  constructor(
    private readonly repository: Repository<User>,
    private readonly logger: Logger
  ) {}
}

组合带来的优势:

  • 依赖清晰。
  • 更容易测试替换。
  • 避免深层继承。
  • 每个能力可独立演进。

六、Mixin

Mixin 用于组合类行为:

ts
type Constructor<T = object> = new (...args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    createdAt = new Date();
  };
}

class Entity {
  constructor(public id: string) {}
}

class TimestampedEntity extends Timestamped(Entity) {}

Mixin 的代价:

  • 类型签名变复杂。
  • 初始化顺序和同名成员可能冲突。
  • 调试时类层次不直观。
  • 装饰器或框架元数据可能不能自然继承。

简单能力优先委托对象或函数组合。

七、装饰器

装饰器会在运行时参与类、方法或字段定义。其语义取决于 TypeScript 版本、配置和框架采用的装饰器体系。

概念示例:

ts
function logged<This, Args extends unknown[], Return>(
  original: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
  return function (this: This, ...args: Args): Return {
    console.log(`调用 ${String(context.name)}`);
    return original.call(this, ...args);
  };
}
ts
class Calculator {
  @logged
  add(left: number, right: number): number {
    return left + right;
  }
}

先确认装饰器体系 不同框架可能依赖旧版实验性装饰器与元数据。不要把新版标准装饰器示例直接混入旧框架,也不要只看语法相似就假设运行行为一致。

八、设计模式的 TypeScript 表达

工厂

ts
function createPayment(kind: "card" | "wallet"): PaymentMethod {
  return kind === "card" ? new CardPayment() : new WalletPayment();
}

策略

ts
type DiscountStrategy = (amount: number) => number;

简单策略直接用函数类型,不必都创建类。

观察者

ts
type Listener<T> = (event: T) => void;

class EventBus<T> {
  private listeners = new Set<Listener<T>>();

  subscribe(listener: Listener<T>): () => void {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  emit(event: T): void {
    for (const listener of this.listeners) listener(event);
  }
}

返回取消订阅函数,明确资源生命周期。

九、instanceof 的适用范围

instanceof 依赖真实原型链:

ts
if (method instanceof CardPayment) {
  // 运行时可判断
}

从 JSON 恢复的普通对象不会自动成为类实例。跨 iframe、重复安装包或多运行上下文时,instanceof 也可能不符合预期。领域状态通常更适合判别字段。

十、常见误区

  1. 为复用几行代码建立深层继承树。
  2. 把大量字段设为 protected,让子类依赖内部布局。
  3. 认为 TypeScript private 是强运行时隐私。
  4. 用类表达所有数据结构和策略。
  5. Mixin 叠加过多,类型和初始化难以理解。
  6. 混用不同装饰器标准。
  7. 认为接口可用于 instanceof
  8. JSON 反序列化后直接断言为类实例。

十一、实践练习

  1. 用接口和抽象类分别表达支付能力,比较差异。
  2. 把三层继承的服务改成组合式依赖。
  3. 实现返回取消函数的类型安全 EventBus。
  4. 用 Mixin 添加时间戳,再改为委托对象,比较可维护性。
  5. 检查项目装饰器配置和框架文档,确认采用哪种语义。

十二、总结

  1. 类在运行时真实存在,接口和类型别名不会。
  2. 抽象类适合共享实现,接口适合能力契约与组合。
  3. 组合通常比深继承更易维护和测试。
  4. Mixin 是高级工具,不应成为默认复用方式。
  5. 装饰器必须结合具体标准、编译配置和框架理解。
  6. 多态不一定依赖类,函数、接口和判别联合也能表达。

请继续阅读:第9章:大型工程。


原始资料引用


  • 第7章:高级类型
  • 第9章:大型工程

本章目录
一、继承与多态二、抽象类与接口三、访问修饰符四、protected 的克制使用五、组合优先于继承六、Mixin七、装饰器八、设计模式的 TypeScript 表达九、instanceof 的适用范围十、常见误区十一、实践练习十二、总结原始资料引用Related Documents
苏ICP备2025204887号-2