软件构造课笔记:第6章——抽象数据类型(ADT)

抽象数据类型(ADT)

抽象和用户定义类型

除了编程语言所提供的基本数据类型和对象数据类型,程序员可定义 自己的数据类型

数据抽象:由一组操作所刻画的数据类型

传统的类型定义:关注数据的具体表示

抽象类型:强调“作用于数据上的操作”,程序员和client无需关心数据如何具体存储的,只需设计 / 使用操作即可。

操作和其Spec完整定义了数 据类型,通过抽象同具体的数 据结构、内存存储和实现分离

ADT是由操作定义的,与其内部如何实现无关

类型分类和操作

类型分为可变和不可变数据类型

可变类型的对象:提供了可改变其内部数据的值的操作

不可变数据类型: 其操作不可改变内部值,而是构造新的对象

还有一些类型提供两种形式

对ADT的操作有如下分类:

1.构造器:创建该类型的新对象

2.生产器:生产者从该类型的旧对象中创建新的对象

3.观察器:观察者接受抽象类型的对象,并返回不同类型的对象

4.变值器:改变对象属性的方法

四者的签名如下

creator:

可能实现为构造函数或静态函数,其中静态方法实现称为工厂方法。

mutator:

变值器通常返回void,意味着其改变了对象某些内部状态。也可能返回某些非空类型,如Set.add()

抽象数据类型实例

int 和String:不可变类型

List:可变类型

creators: ArrayList and LinkedList constructors, Collections.singletonList
producers: Collections.unmodifiableList
observers: size , get
mutators: add , remove , addAll , Collections.sort

设计抽象数据类型

良好 法则”,提供一组操作,设计其行为规约 ADT spec

1.设计一组简单操作,通过简单操作的组合实现复杂的操作。操作的行为应该 是内聚的。

2.要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低

3.要么抽象、要么具体,不要混合---要么针对抽象 设计,要么针对具体应用的设计

表示独立性

client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。

通过前提条件和后置条件充分刻画了ADT的操作,spec规定了client和implementer之间的契约,明确了client知道可以依赖哪些内容,implementer知道可以安全更改的内容。

测试抽象数据类型

测试creators, producers, and mutators:调用observers来观察这些 operations的结果是否满足spec; 

测试observers:调用creators, producers, and mutators等方法产生或 改变对象,来看结果是否正确。

风险:如果被依赖的其他方法有错误,可能导致被测试方法的测试结 果失效。

不变量

ADT 需要始终保持其不变量

不变量:程序在任何时候总是true的性质 

由ADT来负责其不变量,与客户端的任何行为无关。

为什么需要不变量:保持程序的“正确性”,容易发现错误。如果没有不变性,则需要在出错时检查所有改变的地方,不易调试

总是要假设client有“恶意”破坏ADT不变量的 行为--defensive programming

表示泄露:对不变性的威胁,客户端可以直接访问其字段。

不仅影响不变性,也影响了表示独立性:无法在不影响客户端的情况下改变其内部表示。

防御性复制:我们可以通过使用防御性复制来修补表示泄露:例如:创建一个可变对象的副本,以避免泄露对可变rep的引用。

当复制代价很高时,只能够在spec中注明不能够改变返回的对象,但是由此引发的潜在bug也将很多。

除非迫不得已,否则不要把希望寄托于客户端上,ADT有责任保证自己的独立性,并避免表示泄露。

最好的办法就是使用不可变的类型,彻底避免表示泄露。


表示不变性和抽象函数

R:表示值(代表值)的空间由实际实现实体的值组成。

一般情况下ADT的表示比较简单,有些时候需要复杂表示。

A:抽象值构成的空间:client看到和使用的值

ADT开发者关注表示空间R,client关注抽象空间A

1.每个抽象值都由某个代表值(满射)映射到。
2.一些抽象值被多个代表值映射到(未必单射)。
3.并不是所有的代表值都被映射(未必双射)。

抽象函数:R和A之间映射关系的函数,即如何去解释R中的每一个值为A中的每一个值。

1.表示不变性RI:某个具体的“表示”是否是“合法的”
2.也可将RI看作:所有表示值的一个子集,包含了所有合法的表示值
3.也可将RI看作:所有表示值的一个子集,包含了所有合法的表示值

表示不变量和抽象函数都应该记录在代码中,就在代表本身的声明旁边

是什么决定AF和RI?

同一个ADT,可以有多种表示;不同的内部表示,需要设计不同的AF和RI

法”的 (RI) 选择某种特定的表示方式 R ,进而指定某个子集是“合 ,并为该子集中的每个值做出“解释” (AF) —— 即如何映射 到抽象空间中的值

同样的表示空间R,可以有不同的RI

即使是同样的R、同样的RI,也 可能有不同的AF,即“解释不同”

设计ADT:

(1) 选择R和A;
(2) RI ---合法的表示值;
(3) 如何解释合法的表示值---映射AF

做出具体的解释:每个rep value如何映射到abstract value

而且要把这种选择和解释明确写到代码当中

要t随时检查RI是否满足

有益突变

对 immutable 的 ADT 来说,它在 A 空间的 abstract value 应是不变的。
但其内部表示的 R 空间中的取值则可以是变化的

记录AF, RI和表示泄漏的安全声明
在类中记录抽象函数和表示不变性是一种很好的做法,即在声明代表的私有字段的地方使用注释。

要精确的记录RI:rep中的所有fields何为有效
要精确记录AF:如何解释每一个R值、
另一个需要记录的是表示泄漏的安全声明。
给出理由,证明代码并未对外泄露其内部表示。

ADT不变量取代先决条件
用ADT不变量取代复杂的 Precondition,相当于将复杂的precondition封装到了ADT内部。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值