20.抽象类 (Abstract Classes) 副本.md

查看飞书原文 ↗3,703 字符

在我们掌握了 TypeScript 中类的基础之后,便能熟练地创建对象的蓝图。但随着应用复杂度的提升,我们常常会遇到一种新的设计挑战:我们想为一组相关的类提供一个共同的“基类”,这个基类应该包含一些共享的实现(代码),同时又强制所有子类必须提供它们各自独有的实现。

单纯的类做不到“强制”,因为它所有的东西都是具体的。而单纯的接口 (interface) 又做不到“共享实现”,因为它只定义契约,不包含任何代码。为了填补这一空白,TypeScript 提供了抽象类 (Abstract Classes)。

抽象类是一种特殊的类,它充当着“半成品蓝图”的角色,为其他类提供了一个可供扩展的模板。它既能定义具体的实现,又能声明必须由子类完成的“抽象”部分。

什么是抽象类?

抽象类使用 abstract 关键字进行声明。它的核心特点在于可以包含两种类型的成员:

  1. 具体成员 (Concrete Members):与普通类一样,包含具体的实现。这些属性和方法会被所有子类继承。
  2. 抽象成员 (Abstract Members):同样使用 abstract 关键字声明,但不包含任何实现细节。它们只定义一个签名(方法名、参数、返回类型)。任何继承该抽象类的子类,都必须为这些抽象成员提供具体的实现。

让我们通过一个例子来理解。假设我们正在为一个公司构建 HR 系统,系统中存在不同类型的员工,如 Developer 和 Manager。他们都有一些共同点(如姓名和基本信息),但在计算年度奖金的方式上却截然不同。

这时,一个 Employee 抽象类便是完美的选择:

// 声明一个 Employee 抽象类
abstract class Employee {
  // 1. 具体属性,所有子类共享
  public name: string;

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

  // 2. 具体方法,所有子类共享
  public getProfile(): string {
    return `Employee: ${this.name}`;
  }

  // 3. 抽象方法,没有实现,必须由子类提供
  public abstract calculateAnnualBonus(salary: number): number;
}

这个 Employee 类清晰地定义了一个模板:

抽象类的规则与使用

1. 不能被实例化

抽象类是一个不完整的“半成品”,因此你不能直接使用 new 关键字来创建它的实例。它的唯一用途就是被其他类继承。

// const employee = new Employee("John"); 
// 编译时错误: Cannot create an instance of an abstract class.

2. 必须实现所有抽象成员

任何继承自抽象类的子类,都必须为父类中所有的抽象成员提供具体的实现,否则 TypeScript 编译器会报错。

class Developer extends Employee {
  // 子类必须实现 calculateAnnualBonus 方法
  public calculateAnnualBonus(salary: number): number {
    // 开发者的奖金是薪水的 10%
    return salary * 0.1;
  }
}

class Manager extends Employee {
  // 子类必须实现 calculateAnnualBonus 方法
  public calculateAnnualBonus(salary: number): number {
    // 经理的奖金是薪水的 20%
    return salary * 0.2;
  }
}

const dev = new Developer("Alice");
const mgr = new Manager("Bob");

console.log(dev.getProfile()); // "Employee: Alice" (继承自基类)
console.log(dev.calculateAnnualBonus(100000)); // 10000 (子类自己的实现)

console.log(mgr.calculateAnnualBonus(200000)); // 40000 (子类自己的实现)

通过这种方式,抽象类为整个继承体系提供了一个统一的结构和行为保证,确保了没有任何一个“员工”类型会遗漏掉奖金计算的逻辑。

抽象类 vs. 接口:该如何抉择?

抽象类和接口在某些方面看起来很相似,都定义了其他类型必须遵守的契约。但它们的根本目的和能力有着本质区别。

特性 抽象类 (abstract class) 接口 (interface)
核心目的 提供模板和共享代码 定义纯粹的契约或形状
实现 可以包含具体实现的方法和属性 不能包含任何实现(只定义签名)
构造函数 可以有构造函数 不能有构造函数
继承关系 子类使用 extends 关键字,并且只能继承一个抽象类 类使用 implements 关键字,一个类可以实现多个接口
语义 “是一个” (is-a) 的强关系,强调继承和共享行为 “能做” (can-do) 的能力关系,强调功能和行为的约定

决策指南

总结

抽象类不仅仅是一种语法,更是一种强大的面向对象设计工具。它在“完全抽象的接口”和“完全具体的类”之间,提供了一个完美的平衡点。

它允许我们为一组相关的类定义一个共同的、可复用的骨架,同时通过强制子类实现抽象方法,来保证整个继承体系的完整性和一致性。当你需要为你的类层次结构提供一种“架构性指导”,确保所有派生类都遵循某个核心模板时,抽象类便是你最得力的盟友。