第六章 抽象数据类型(ADT)
第六章 抽象数据类型(ADT)
ADT
-
由一组操作所刻画的数据类型,而非数据的具体表示(传统类型)
-
强调"作用于数据上的操作",程序员和 client无需关心数据如何具体存储的,只需设计/使用操作即可。
-
ADT是由操作定义的,与其内部如何实现无关!
ADT分类
可变数据类型
- 提供了可改变其内部数据的值的操作
不可变数据类型
- 其操作不改变内部值,而是构造新的对象
有时一个类型会以两种形式提供,一种是可变的,一种是不可变的。 例如,StringBuilder 是 String 的可变版本(但两者肯定不是同一 Java 类型,并且不可互换)。
ADT操作
构造器(Creators)
-
以其他类型为参数创建当前类型,也就是说不能接受被构造的类型的对象。
-
两种方式
-
构造函数(constructor)
- 通过new创建
-
静态工厂方法
-
生产器(Producers)
- 由旧对象创建新对象
观察器( Observers)
- 以当前对象类型为参数,返回不同类型的对象
变值器(Mutators)
- 更改对象属性
设计好的ADT
-
设计简洁、一致的操作
- 例如,我们可能不应该向 List 添加求和运算。 它可能会帮助处理整数列表的客户,但是字符串列表呢?
-
要足以支持 client 对数据所做的所有操作需要,且 用操作满足 client 需要的难度要低
- 例如,没有get()操作就无法获取list的内部数据
- 例如,对于 List 来说 size 方法并不是绝对必要的,因为我们可以对递增的索引应用 get 直到失败,但这是低效且不方便的。 用遍历方式获取list的size太复杂,应该提供size()操作,方便客户端使用
表示独立性(Representation Independence)
-
client 使用 ADT 时无需考虑其内部如何实现, ADT 内部表示的变化不应影响外部 spec 和客户端。
-
除非 ADT 的操作指明了具体的 pre-和 post-condition ,否则不能改变 ADT 的内部表示 ——spec 规定了client 和 implementer 之间的契约。
-
不能暴露属性,属性尽量用private,不要让用户获取到属性的引用
ADT测试
-
测试 creators, producers, and mutators:调用observers来观察这些operations的结果是否满足spec;
-
测试observers:调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确。
风险:如果被依赖的其他方法有错误,可能导致被测试方法的测试结果失效。
不变性(Invariants)
-
不变量是程序的一个属性,对于程序的每个可能的运行时状态,它始终为真。
-
immutability就是一个典型的“不变量”
-
由ADT来负责其不变量,与client端的任何行为无关
- 它不依赖于客户端的良好行为
- 正确性不依赖于其他模块
为什么需要不变量
- 保持程序的“正确性”,容易发现错误
表示泄露
-
不仅影响不变性,也影响了表示独立性
-
风险
- 一旦泄露,ADT内部表示可能会在程序的任何位置发生改变(而不是限制在ADT内部),从而无法确保ADT的不变量是否能够始终保持为true。
-
避免泄露的方法
-
使用private和public修饰域
-
使用final
-
接受mutable类型作为自己属性,或者返回自己的mutable类型时
- 复制一份信息的
- 在规约中提出调用者不能更改(当复制代价很高时)
-
检查不变性
- 调用方法后是否满足不变性
- 是否有表示泄露
AF(Abstraction Function)和RI(Rep Invariant)
值的两个空间
-
表示空间R rep values
- ADT开发者关注
-
抽象空间A abstract values
- client看到和使用的值
R和A的关系
-
满射
- A中每个值都对应R中某个值或某些值
-
未必单射
- A中某个值在R中对应多个值(不是一对一,就是多对一)
-
未必双射
- R中存在值在A中没有对应的值
抽象函数 AF Abstraction Function
- R和A之间映射关系的函数,即如何去解释R中的 每一个值为A中的每一个值。
表示不变性 RI
- 某个具体的“表示”是否是“合法的”
- 在对象的初始状态不变量为true,在对象发生变化时,不变量也要为true
不同的内部表示,需要设计不同的AF和RI
选择某种特定的表示方式R,进而指定某个子集是“合法”的(RI),并为该子集中的每个值做出“解释”(AF)——即如何映射到抽象空间中的值。
即使是同样的R、同样的RI,也可能有不同的AF,即“ 解释不同“
AF(s) 代表括号中的R域中的值映射到A域中的值
用户需要知道
- A空间
- ADT的四个器
判断RI
-
RI是规则,ADT从始至终都需满足
-
区分AF和RI
checkRep
-
好处
- 尽早捕获错误
-
用处
- 在所有可能改变rep的方法内都要检查,Observer方法可以不用,但建议也要检查,以防止你的"万一"
- 每个方法return之前,用checkRep()检查不变量是否得以保持。
有益的可变性
对immutable的ADT来说,它在A空间的abstract value应是不变的,但其内部表示的R空间中的取值则可以是变化的。这种mutation 只是改变了 R 值,并未改变 A 值,对 client 来说是immutable的“ AF 并非单射”,从一个R值变成了另一个R值, 但这并不代表在immutable的类中就可以随意出现mutator!
记录AF, RI和Safety from Rep Exposure
-
要精确的记录RI:rep中的所有fields何为有效
-
要精确记录AF:如何解释每一个R值
-
表示泄漏的安全声明:给出理由,证明代码并 未对外泄露其内部表示——自证清白
ADT的规约
-
ADT的规约里只能使用client可见的内容来撰写,包括参数、返回值、异常等。
-
如果规约里需要提及“值”,只能使用A空间中的“值”。
-
ADT的规约里也不应谈及任何内部表示的细节,以及R空间中的任何值
-
ADT的内部表示(私有属性)对外部都应严格不可见
-
故在代码中以注释的形式写出AF和RI而不能在Javadoc文档中,防止被外部看到而破坏表示独立性/信息隐藏
用ADT不变量取代复杂的Precondition
-
相当于将复杂的precondition封装到了ADT内部
-
把输入的参数,封装成一个特点的数据类型