20.抽象类 (Abstract Classes) 副本.md 来源:https://my.feishu.cn/file/FtLnbrrL8oKlsIxdhErcrmQknWc 采集状态:已完成正文采集;已从查看代码模式逐行提取,行号 1 至 115 连续,并核对滚动到底。 在我们掌握了 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`)。 ### 总结 抽象类不仅仅是一种语法,更是一种强大的面向对象设计工具。它在“完全抽象的接口”和“完全具体的类”之间,提供了一个完美的平衡点。 它允许我们为一组相关的类定义一个共同的、可复用的骨架,同时通过强制子类实现抽象方法,来保证整个继承体系的完整性和一致性。当你需要为你的类层次结构提供一种“架构性指导”,确保所有派生类都遵循某个核心模板时,抽象类便是你最得力的盟友。