

在我们掌握了 TypeScript 中类的基础之后，便能熟练地创建对象的蓝图。但随着应用复杂度的提升，我们常常会遇到一种新的设计挑战：我们想为一组相关的类提供一个共同的“基类”，这个基类应该包含一些共享的实现（代码），同时又强制所有子类必须提供它们各自独有的实现。

单纯的类做不到“强制”，因为它所有的东西都是具体的。而单纯的接口 (`interface`) 又做不到“共享实现”，因为它只定义契约，不包含任何代码。为了填补这一空白，TypeScript 提供了**抽象类 (Abstract Classes)**。

抽象类是一种特殊的类，它充当着“半成品蓝图”的角色，为其他类提供了一个可供扩展的模板。它既能定义具体的实现，又能声明必须由子类完成的“抽象”部分。

### 什么是抽象类？

抽象类使用 `abstract` 关键字进行声明。它的核心特点在于可以包含两种类型的成员：

1.  **具体成员 (Concrete Members)**：与普通类一样，包含具体的实现。这些属性和方法会被所有子类继承。
2.  **抽象成员 (Abstract Members)**：同样使用 `abstract` 关键字声明，但**不包含任何实现细节**。它们只定义一个签名（方法名、参数、返回类型）。任何继承该抽象类的子类，都**必须**为这些抽象成员提供具体的实现。

让我们通过一个例子来理解。假设我们正在为一个公司构建 HR 系统，系统中存在不同类型的员工，如 `Developer` 和 `Manager`。他们都有一些共同点（如姓名和基本信息），但在计算年度奖金的方式上却截然不同。

这时，一个 `Employee` 抽象类便是完美的选择：

```typescript
// 声明一个 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` 类清晰地定义了一个模板：
*   每个员工都有 `name` 和一个 `getProfile` 方法。
*   每个员工都**必须**有一种计算年度奖金 (`calculateAnnualBonus`) 的方式，但具体怎么算，基类并不关心，由各个子类自己决定。

### 抽象类的规则与使用

#### 1. 不能被实例化

抽象类是一个不完整的“半成品”，因此你**不能直接使用 `new` 关键字来创建它的实例**。它的唯一用途就是被其他类继承。

```typescript
// const employee = new Employee("John"); 
// 编译时错误: Cannot create an instance of an abstract class.
```

#### 2. 必须实现所有抽象成员

任何继承自抽象类的子类，都必须为父类中所有的抽象成员提供具体的实现，否则 TypeScript 编译器会报错。

```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`) 的能力关系，强调**功能**和**行为的约定**     |

#### 决策指南

*   **选择抽象类，当你...**
    *   需要在一组紧密相关的类之间**共享代码**（方法实现、属性值）。
    *   想为这些类提供一个共同的、包含基础功能的基类。
    *   能确定这些类之间存在明确的“is-a”层次关系（例如，`Dog` is an `Animal`）。

*   **选择接口，当你...**
    *   只需要定义一个**行为契约**，而不关心其具体实现。
    *   希望为不相关的类提供共同的功能（例如，`Button` 和 `Hyperlink` 都可以 `implements Clickable`）。
    *   希望一个类能够拥有多种“类型”或“能力”（例如，一个类可以同时实现 `Serializable` 和 `Loggable`）。

### 总结

抽象类不仅仅是一种语法，更是一种强大的面向对象设计工具。它在“完全抽象的接口”和“完全具体的类”之间，提供了一个完美的平衡点。

它允许我们为一组相关的类定义一个共同的、可复用的骨架，同时通过强制子类实现抽象方法，来保证整个继承体系的完整性和一致性。当你需要为你的类层次结构提供一种“架构性指导”，确保所有派生类都遵循某个核心模板时，抽象类便是你最得力的盟友。

