CLASS 06 · 2026-02-23 · 抽象
抽象数据类型
抽象数据类型(ADT)是「便于修改」这一品质的核心工具。本讲学习类型由其操作定义的思想,以及表示独立性如何让使用者不受内部实现变化的影响。
什么是抽象
抽象相关的几个核心概念:
- 抽象(Abstraction):省略或隐藏低层细节。
- 模块化(Modularity):把系统划分为组件。
- 封装(Encapsulation):在模块周围建起一道墙。
- 信息隐藏(Information hiding):隐藏模块的实现细节。
- 关注点分离(Separation of concerns):让每个功能成为单一模块的职责。
用户自定义类型
数据抽象的核心思想:一个类型由你能对它执行的操作来刻画。
使用者不需要知道一个类型「内部是什么」,只需要知道「它能做什么」。这就是抽象的力量。
操作的分类
抽象类型的操作可以分为四类:
- 创建者(Creator):创建该类型的新对象。例如构造函数。
- 生产者(Producer):从现有对象创建新对象。例如字符串的
concat。 - 观察者(Observer):接受抽象类型对象,返回其他类型的值。例如
size()、isEmpty()。 - 修改者(Mutator):改变对象。例如
add()、remove()。
抽象类型由其操作定义
类型 \(T\) 的操作集合及其规格,完全刻画了「\(T\) 是什么」。两个类型如果提供相同的操作且规格相同,则从使用者角度看它们就是相同的类型。
// 一个不可变整数集合的 ADT
class ImmutableSet<T> {
// 创建者
constructor(elements: T[]) { /* ... */ }
// 生产者
add(elem: T): ImmutableSet<T> { /* 返回新集合 */ }
remove(elem: T): ImmutableSet<T> { /* 返回新集合 */ }
// 观察者
contains(elem: T): boolean { /* ... */ }
size(): number { /* ... */ }
isEmpty(): boolean { /* ... */ }
}
设计抽象类型
好的 ADT 应遵循以下原则:
- 少量简单的操作:API 应精简。
- 每个操作有明确目的:不做多余的事。
- 操作集合完备:能完成使用者需要的所有事。
- 不混用通用与领域特定功能:保持抽象纯粹。
表示独立性
表示独立性(Representation independence):抽象类型的使用独立于其内部表示。
这意味着我们可以把一个 ImmutableSet 的内部实现从数组换成链表、从哈希表换成平衡树,而使用者代码完全不需要修改——只要操作的行为(规格)保持不变。
// 表示方式一:用数组存储
class ImmutableSetArr<T> {
private readonly elements: T[];
// ...
}
// 表示方式二:用链表存储
class ImmutableSetList<T> {
private readonly head: ListNode<T> | null;
// ...
}
// 两种实现满足同一 ADT 规格,使用者代码无需修改
在 TypeScript 中实现 ADT
TypeScript 提供了多种实现 ADT 的方式:类、接口 + 类、枚举等。接口特别适合表达「操作集合」这一抽象,而类负责具体实现。
测试抽象数据类型
为 ADT 构建测试套件的方法:为它的每个操作编写测试。测试应验证每个操作是否满足其规格,并验证操作之间的交互是否正确。
核心要点
操作定义类型ADT 由其操作集合及规格完全刻画。
四类操作创建者、生产者、观察者、修改者。
表示独立内部表示可自由更换,使用者代码不受影响。