CLASS 06 · 2026-02-23 · 抽象

抽象数据类型

抽象数据类型(ADT)是「便于修改」这一品质的核心工具。本讲学习类型由其操作定义的思想,以及表示独立性如何让使用者不受内部实现变化的影响。

什么是抽象

抽象相关的几个核心概念:

用户自定义类型

数据抽象的核心思想:一个类型由你能对它执行的操作来刻画。

使用者不需要知道一个类型「内部是什么」,只需要知道「它能做什么」。这就是抽象的力量。

操作的分类

抽象类型的操作可以分为四类:

抽象类型由其操作定义

类型 \(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 应遵循以下原则:

表示独立性

表示独立性(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 由其操作集合及规格完全刻画。

四类操作创建者、生产者、观察者、修改者。

表示独立内部表示可自由更换,使用者代码不受影响。