3.3抽象数据类型(ADT)
ADT特性:
(用户看不见AF、RI)
- 表示泄露
- 抽象函数AF
- 表示不变量RI
抽象数据类型:把所有的属性改为private
可变类型的对象:提供了可改变其内部数据的值的操作
不变数据类型:其操作不改变内部值,而是构造新的对象
ADT操作的四种类型:
- ※Creators(构造器):创建类的对象,可能实现为构造函数或静态函数。
调用工厂方法来解决用户和构造器间的矛盾 - Producers(生产器):将旧的对象生成新的对象,
concat: String*String➡String - Observers(观察器):给一个当前类的对象,返回一个其他类型的、可被用户观察到的值
size: List➡int - ※Mutators(变值器):改变属性的值,返回值可有可无,通常返回void类型
immutable类型的类只包括前三种操作,第四种操作只存在于mutable类型的类中
设计抽象类型,把抽象数据类型涉及的操作方法都列出来即可
- 设计简洁、一致的操作
- 提供足够多的操作以支持类的使用
没有get()操作就无法获取list的内部数据;
用size()操作获取list的size,方便client使用
※表示独立性
表示独立性:client使用ADT时不需要考虑内部如何实现,ADT内部变化不应影响外部spec和客户端
可以把所有的属性设为private来让类满足表示独立性
测试ADT:
测试creators, producers, and mutators:调用observers来观察这些operations的结果是否满足spec
测试observers:调用creators, producers,and mutators等方法产生或改变对象,来看结果是否正确
风险:如果被依赖的其他方法有错误,可能导致被测试方法的测试结果失效
※不变性:
不变量:任何时候都是true
- immutability就是一个典型的不变量
由ADT负责不变量,与client的任何行为无关
需要不变量来保持程序的“正确性”,容易发现错误
RI & AF:
抽象值构成的空间:client看到和使用的值
ADT开发者关注表示空间R,client关注抽象空间A
用字符串对集合内的数据进行存储时,需要做一个映射,需要找到两个空间的联系
R与A空间满足的特性:
- 满射:A空间任何一个数据在R空间都有对应的值
- 未必单射:A空间内的任何一个数据可能对应R空间的多个值
- 未必双射:R空间中可能有数据不与A空间的数据对应。R中的部分值并非合法的,在A中无映射值。
抽象函数:R和A直接映射关系的函数,即如何去解释R中的每一个值为A中的每一个值
AF:R→A
表示不变性RI(Rep invariant):
某个具体的“表示”是否是“合法的”;
所有表示值的一个子集,包含了所有合法的表示值;
一个条件,描述了什么是“合法”的表示值
RI:R→boolean
不包含重复字符的字符串,都是“合法的”表示值
不同的内部表示,需要设计不同的AF和RI
选择某种特定的表示方式R,进而指定某个子集是“合法的”(RI),并为该子集中的每个值做出“解释”(AF)——即如何映射到抽象空间中的值
即便是同样的R、同样的RI,也可能有不同的AF,即“解释不同”
设计ADT:
①选择R和A;
②如何解释合法的表示值——映射AF;
③RI——合法的表示值(判定条件)
做出具体的解释:每个rep value如何映射到abstract value
用户在使用类的时候只需要知道方法的描述以及对应方法的操作列表和返回值,用户不需要知道rep、AF和RI;
开发人员需要知道rep、AF、RI、creators、observers等全部内容。
随时检查RI是否满足,在所有可能改变rep的方法内都要检查,可以不用observer,但最好检查下
当immutable的属性值改变不会影响A空间内数据的变化时,immutable的属性值是可以变化的
要精确的记录RI:rep中的所有fields何为有效
要精确记录AF:如何解释每一个R值
表示泄露的安全声明:给出理由,证明代码并未对外泄露其内部表示(自证清白)
ADT的规约是给client看的,包括参数、返回值和异常等;
如果规约里需要提及“值”,只能使用A空间的“值”;
ADT规约中不应谈及任何内部表示的细节,以及R空间中的任何值;
ADT的内部表示(私有属性)对外都应严格不可见
检测不变性的三个标准:
- 不能出现表示暴露
- creators、producers存储的数据合法
- mutators、observers方法中改变属性的值是合法的