第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 也可能不符合预期。领域状态通常更适合判别字段。
十、常见误区
- 为复用几行代码建立深层继承树。
- 把大量字段设为 protected,让子类依赖内部布局。
- 认为 TypeScript private 是强运行时隐私。
- 用类表达所有数据结构和策略。
- Mixin 叠加过多,类型和初始化难以理解。
- 混用不同装饰器标准。
- 认为接口可用于 instanceof。
- JSON 反序列化后直接断言为类实例。
十一、实践练习
- 用接口和抽象类分别表达支付能力,比较差异。
- 把三层继承的服务改成组合式依赖。
- 实现返回取消函数的类型安全 EventBus。
- 用 Mixin 添加时间戳,再改为委托对象,比较可维护性。
- 检查项目装饰器配置和框架文档,确认采用哪种语义。
十二、总结
- 类在运行时真实存在,接口和类型别名不会。
- 抽象类适合共享实现,接口适合能力契约与组合。
- 组合通常比深继承更易维护和测试。
- Mixin 是高级工具,不应成为默认复用方式。
- 装饰器必须结合具体标准、编译配置和框架理解。
- 多态不一定依赖类,函数、接口和判别联合也能表达。
请继续阅读:第9章:大型工程。
原始资料引用