ADT 总结
1. ADT的内容
属性(名词) | rep | 内部数据结构 | 不可见 |
---|---|---|---|
方法(动词) | Spec规约 impl实现 | 方法,功能 | Spec可见 impl不可见 |
对外部只有接口,封装后用户不关心内部
1.1 ADT的内部方法可以大致分为以下四类
- creator构造器
初始化,新建一个ADT对象
a. new() b. 静态工厂:graph.empty()
- producer生产器
比如 String.concat() 能够返回一个新的对象, 不改变内部数据
- observer观察器
Eg: .size() 看内部东西,不改变内部数据
- mutator变值器
Eg. Add() 修改内部数据
大部分返回void / bool
只有mutable类型才有!
1.2 表示独立性
-
内部变化后,外部不因此受影响
-
为什么内部会变化(impl)?(优化性能,多种实现)
-
内部变化一定要遵循spec! (测试用例就是根据spec写的)
对于客户端,只能见到spec,无法看见rep和impl
比如:List 可以有两种实现方式:
List a =New arrayList
List b = New linkeList
但是从外部看来,调用方法并没有差异(比如.Add() 、 .remove() 方法是一样的)
-
内部rep应该是private而不是public
-
用户端使用的API 不应该因为内部rep实现的改变而瘫痪
1.3 表示泄漏/ rep exposure:
个人理解:client获取到了你内部变量/获取到了你的内部实现方法(本来不应该看到的,给人看到了!)
如何避免表示泄漏:
- 全部属性private
- 对于mutable类型
- Collection.unmodifiable.wraped) (解决拷贝空间问题)
- 防御式拷贝(返回一个拷贝后的新的对象)
- 尽可能用immutable数据类型
1.4 如何测试一个ADT的方法(测试用例)
- adt的四种方法、
- c/p/m 用observer查看属性
- O 先用c/p 造出,再调用o
- 代码覆盖率无须100%
2. ADT的不变性
“始终保持为真的一组条件”
2.1 抽象空间A ,实际空间R
- 抽象空间A:用户看到的值;(Abstract)
- 实际空间R:内部维系的值(Realization)
A中的值,一定能够在R中至少找到一个值与之对应(满射)
(但是不是单射不一定)
2.2 AF RI 概念
-
AF(abstract function)抽象函数(就是A --R之间的函数)
- 抽象函数是表示从表示空间到抽象空间映射的函数比如:我存了一个graph图来表示人际关系
AF(graph)=现实中的人际关系图
-
RI(rep invariant) 表示不变量
-
表示不变量将表示变量值映射成一个布尔值。
简单来说,就是一个条件,判断内部元素是否合法。
比如:我要求图graph不为空,那么RI中就是“图不为空”这个条件。
-
Checkrep():
满足条件,合法; 不满足,非法!
使得属性都满足RI为true
每个方法返回之前调用
用Assert +表达式
False: throw AssertErro(直接退出!违反了RI,程序没有必要继续错误地执行)
调用时机:
C/P/M 最后一行返回之前。
O 按道理来说不会改变RI,但是防止粗心,也需要在最后写一个。
每次都调用,增加复杂度?
开关!
-ea 开发的时候,打开assert
-da 交付的时候, 关闭assert
-
[参考:AF RI 的概念](表示不变量(Representation Invariant)与抽象函数(Abstract Function) - siren27 - 博客园 (cnblogs.com))
- unmodifiableList 返回一个只能看不能改的方法
3.ADT的设计流程
-
设计一组方法(C/T/M/O) 以及spec
-
TDD 测试用例 (根据spec)
-
选择Rep(内部数据结构)/并impl
-
设计RI (比如 边的权值不为负,点不能为空)
-
设计AF (R—>A) (比如 边对应 人际关系)
-
RI/AF 的注释
-
- 怎么避免表示泄漏?(mutable与immutable都要考虑)
4.ADT如何保持表示不变性
-
对于immutable,只要保证没有/M
-
对于RI:checkRep()
-
无表示泄漏!(见1.3)