抽象数据类型(ADT)
- Abstraction and User-Defined Types
- Classifying Types and Operations
- Abstract Data Type Examples
- Designing an Abstract Type
- Representation Independence (RI)
- Testing an ADT
- Invariants
- Rep Invariant and Abstraction Function
- Beneficent mutation
- Documenting the AF, RI, and Safety from Rep Exposure
- ADT invariants replace preconditions
Abstraction and User-Defined Types
除了编程语言所提供的基本数据类型和对象数据类型,程序员可定义自己的数据类型
Data Abstraction(数据抽象)
由一组操作所刻画的数据类型
传统的类型定义:关注数据的具体表示
抽象类型:强调“作用于数据上的操作”,程序员和client无需关心数据如何具体存储的,只需设计/使用操作即可。
Classifying Types and Operations
Mutable and immutable types
可变类型的对象:提供了可改变其内部数据的值的操作
不变数据类型: 其操作不改变内部值,而是构造新的对象
Classifying the operations of an abstract type
- Creators(构造器): create new objects of the type.
- Producers(生产器): create new objects from old objects of the type.
- Observers(观察器): take objects of the abstract type and return objects of a different type.
- Mutators(变值器): change objects. 改变对象属性的方法
Abstract Data Type Examples
- int: immutable, so it has no mutators.
-
- creators: the numeric literals 0 , 1 , 2 , …
-
- producers: arithmetic operators + , - , * , /
-
- observers: comparison operators == , != , < , >
-
- mutators: none (it’s immutable)
- String: Java’s string type. String is immutable.
-
- creators: String constructors
-
- producers: concat , substring , toUpperCase
-
- observers: length , charAt
-
- mutators: none
- List: Java’s list type and is mutable.
-
- creators: ArrayList and LinkedList constructors, Collections.singletonList
-
- producers: Collections.unmodifiableList
-
- observers: size , get
-
- mutators: add , remove , addAll , Collections.sort
Designing an Abstract Type
Designing an abstract type involves choosing good operations and determining how they should behave. 设计好的ADT,靠“经验法则”,提供一组操作,设计其行为规约 spec
- 设计简洁、一致的操作
- 要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低
- 要么抽象、要么具体,不要混合 — 要么针对抽象设计,要么针对具体应用的设计
Representation Independence (RI)
表示独立性:client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。
除非ADT的操作指明了具体的pre-condition和post-condition,否则不能改变ADT的内部表示——spec规定了client和implementer之间的契约。
Testing an ADT
- 测试creators, producers, and mutators:调用observers来观察这些
operations的结果是否满足spec; - 测试observers:调用creators, producers, and mutators等方法产生或
改变对象,来看结果是否正确。 - 风险:如果被依赖的其他方法有错误,可能导致被测试方法的测试结
果失效。
Invariants
The most important property of a good abstract data type is that it preserves its own invariants. (保持不变量)
不变量:在任何时候总是true,例如:immutability就是一个典型的“不变量”
由ADT来负责其不变量,与client端的任何行为无关
为什么需要不变量:保持程序的“正确性”,容易发现错误
总是要假设client有“恶意”破坏ADT的不变量—defensive programming
Rep Invariant and Abstraction Function
Abstraction Function
一般情况下ADT的表示比较简单,有些时候需要复杂表示
抽象值构成的空间:client看到和使用的值
- ADT开发者关注表示空间R,client关注抽象空间A
- Every abstract value is mapped to by some rep value (surjective, 满射).
- Some abstract values are mapped to by more than one rep value (not injective, 未必单射).
Abstraction Function(抽象函数):R和A之间映射关系的函数,即如何去解释R中的每一个值为A中的每一个值。
Rep Invariant: another important ADT invariants
- 表示不变性RI:某个具体的“表示”是否是“合法的”
- 也可将RI看作:所有表示值的一个子集,包含了所有合法的表示值
- 也可将RI看作:一个条件,描述了什么是“合法”的表示值
选择某种特定的表示方式R,进而指定某个子集是“合法”的(RI),并为该子集中的每个值做出“解释”(AF)——即如何映射到抽象空间中的值。
即使是同样的R、同样的RI,也可能有不同的AF,即“解释不同”。
设计ADT:
- 选择R和A;
- RI — 合法的表示值;
- 如何解释合法的表示值 —映射AF
- 做出具体的解释:每个rep value如何映射到abstract value,而且要把这种选择和解释明确写到代码当中
Checking the Rep Invariant 随时检查RI是否满足
Beneficent mutation
- 对immutable的ADT来说,它在A空间的abstract value应是不变的。
- 但其内部表示的R空间中的取值则可以是变化的。
- 这种mutation只是改变了R值,并未改变A值,对client来说是immutable的 →“AF并非单射”,从一个R值变成了另一个R值
- 但这并不代表在immutable的类中就可以随意出现mutator!
- 通过牺牲immutability的部分原则来换取“效率”和“性能”
-
- Caching:例如通过cache暂存某些频繁计算的结果
-
- Data structure rebalancing:例如对tree数据结构进行插入或删除节点之后
-
- Lazy computation:上例中在toString()的时候才进行约分计算
Documenting the AF, RI, and Safety from Rep Exposure
- 在代码中用注释形式记录AF和RI
- 要精确的记录RI:rep中的所有fields何为有效
- 要精确记录AF:如何解释每一个R值
- 表示泄漏的安全声明(Safety from Rep Exposure):给出理由,证明代码并未对外泄露其内部表示——自证清白
- ADT的规约里只能使用client可见的内容来撰写,包括参数、返回值、异常等。
- 如果规约里需要提及“值”,只能使用A空间中的“值”。
- ADT的规约里也不应谈及任何内部表示的细节,以及R空间中的任何值
- ADT的内部表示(私有属性)对外部都应严格不可见,故在代码中以注释的形式写出AF和RI而不能在Javadoc文档中,防止被外部看到而破坏表示独立性/信息隐藏
ADT invariants replace preconditions
用ADT不变量取代复杂的Precondition,相当于将复杂的precondition封装到了ADT内部。
- 更加安全
- 更加易于理解
- 更加准备好迎接变化