软件构造第三章3.3
抽象数据类型(ADT)
避免客户端获取数据的内部表示 (表示泄露)
- 特征
- 不变量
- 表示泄露
- 抽象函数AF
- 表示不变量RI
- 关键
- 抽象
- 模块化
- 封装
- 信息隐藏
- 关注点分离
数据抽象
有一组操作所刻画的类型,类型的特征是可以对其执行的操作
分类类型和操作
- 可变(mutable)和不可变(immutable)类型
可变类型的对象可以更改,提供了可改变内部数据值的操作。
不可变数据类型其操作不改变数据内部的值,而是构造一个新对象。 - 抽象类型的操作进行分类
- 构造器(creators)
创建该类型的新对象 - 生产器(producers)
从类型的旧对象创建一个新对象,比如字符串的拼接 - 观察器(observers)
或缺抽象类型的对象并返回不同类型的对象 - 变值器(mutators)
改变对象属性的方法
- 构造器(creators)
设计一个抽象类型
- 设计简洁,一致的操作
- 足以支持客户端对数据所做的所有操作需要,且用操作满足客户端需要的难度要低
- 要么抽象,要么具体,不要混合
表示独立性(representation independence)RI
客户端使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端
测试ADT
- 测试creators, producers, and mutators:调用observers来观察这些 operations的结果是否满足spec;
- 测试observers:调用creators, producers, and mutators等方法产生或 改变对象,来看结果是否正确。
不变量(invariants)
在任何时候总是true,immutable类型就是不变量,由ADT来负责,与客户端任何行为无关。
- 作用:保持程序的正确性,不变量是永远不会改变的,因此在所有用到的地方都要检查不变量是否改变
- private 类内部使用
- public 类外部可以使用
- final 强调不可变类型不会再改变了
- 如何做到不变:
防御式拷贝,意思就是,对于一些本来就可变的数据,比如说像list,date这些,本来就可以改变,如果构造器只是简单的this.time=time;类似这种,他是和传入的对象一直相关,也就是说外部的time改变,里面的就会改变,因此可以this.time=new …
RI和AF
- R 表示值的空间,ADT关注的
- A 抽象值的空间:客户端看到和使用的值,客户端关注的
AF:R->A 满射,未必单射,未必双射,R中的部分值并非合法的,在A中无映射值。
RI:定义AF的表示值(rep值)的子集,描述了什么是合法的表示值
综合来说,选择某种特定的表示方式R,进而指定某个子集是合法的(RI),并且为该子集的每个值做出解释(AF)—即如何映射到抽象空间的值 - 客户端知道的
abstract value space
creators
observers - 开发者知道的
abstract value space
AF
creators
observers
Rep
RI
beneficent mutation (有益突变)
只要映射到空间A的值不变,我们就可以改变R空间的取值,但是客户端看不到改变
作用
- 性能改进
cache暂存某些频繁计算的结果
数据结构再平衡 - 通过牺牲immutability的部分原则来换取效率和性能
记录AF,RI,safety from rep exposure
用注释形式记录
- RI:
rep中的所有fields何为有效 - AF:
如何解释每一个R值 - safety from rep exposure:
给出理由,证明代码并未对外泄露其内部表示 - spec:
规约,只能使用客户端可见的内容来撰写,包括参数,返回值,异常等,不应该谈及任何内部表示的细节,以及R空间的任何值
表示泄露发生,则ADT内部表示可能会在程序的任何位置发生改变 - ADT是否保持不变量标准
- 由构造器或者生产器建立
- 由观察器或者变值器保存
- 没有表示泄露发生
比如说返回一个数组,就会发生表示泄露
ADT不变量替换前置条件
用ADT不变量取代复杂的precondition,相当于将复杂的precondition封装到了ADT内部。
这样易于更改,易于理解,且避免出错
summary
-
抽象数据类型以其操作为特征。
-
操作可分为创造者、生产者、观察者和变异者。
-
ADT的规范是其操作集及其规范。一个好的ADT是简单的、连贯的、充分的和独立于表现的。
-
ADT通过为其每个操作生成测试进行测试,但在相同的测试中同时使用创建者、生产者、变异者和观察者。
-
safe from bugs。一个好的ADT为一个数据类型提供了一个定义良好的契约,这样客户就知道从数据类型中期望得到什么,而实现者有定义良好的自由来改变。
-
easy to understand。一个好的ADT将其实现隐藏在一组简单的操作后面,这样使用ADT的程序员只需要了解操作,而不需要了解实现的细节。
-
ready for change。表示独立性允许对抽象数据类型的实现进行更改,而不需要对其客户端进行更改。
-
不变量是在对象的生存期内,ADT对象实例始终为真的属性。
-
一个好的ADT保留它自己的不变量。不变量必须由创造者和生产者建立,并由观察者和变异者保存。
-
rep不变量指定表示的合法值,并应在运行时使checkRep()进行检查。
-
抽象函数将具体表示映射到它所表示的抽象值。
-
表示泄露威胁着表示独立性和不变量的保存。