哈工大软件构造第6章笔记

6 抽象数据类型 ADT

6.1 抽象和用户定义类型

编程语言具有内置类型(如整数、布尔值、字符串等)和内置过程(如输入和输出)。

用户可以定义自己的数据类型和过程——用户定义的类型。

数据抽象:由一组操作刻画的数据类型,强调“作用于数据上的操作”,程序员和客户端无需关心数据的具体存储方式,只需要设计/使用操作即可。

抽象数据类型T的操作和规约刻画了T 的特征

6.2 分类类型和操作

6.2.1 可变和不可变数据类型

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

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

通常,编程语言对于一些类型会提供两种方式,如string和stringbuilder

6.2.2 四种操作

构造器:(从无到有)创建该类型的新对象

生产器:(从有到新)从该类型的旧对象创建新对象,如String的concat()方法

观察器:获取抽象类型的对象并返回不同类型的对象,如.size()

变值器:改变对象属性的方法,如.add()

eg:

6.2.3 方法签名

构造器:构造函数或者静态函数(实现为静态方法的构造器通常称为工厂方法,如String.valueOf(Object Obj) (返回参数的字符串形式))

变值器:返回值如果为void,则必然意味着它改变了对象的某些内部状态。不过也有可能返回非空类型,如Set.add()返回一个boolean来明确说明是否成功;Component.add()返回对象本身。

6.3 抽象数据类型示例

倒数第二个:从可变数据类型编程不可变数据类型,是从已有对象生成新的

最后一个:读取每一行的文件指针,每次读完之后指向新的行

6.4 设计抽象数据类型

良好的ADT设计:靠“经验法则”,提供一组操作,设计其行为规约spec

spec中应有的内容:参数、返回值、异常等

spec中不应谈及任何内部表示的细节,以及R空间中的任何值

因此,AF和RI应该写在注释里,而不能写在javadoc中,否则表示泄漏

1.设计简洁、一致的操作
2.提供功能要足够强,足以满足用户需求。

判断是否满足用户需求的方法:看对象的每个需要被访问到的属性是否能被访问到

3.要么针对抽象操作,要么针对具体应用的设计:

类型可以是泛型的:例如,列表、集合或图形;或者它可能是特定领域的:街道地图、员工数据库、电话簿等。

但是不可以是混合以上两种情况的。面向具体应用的类型不应该包含通用方法,面向通用的类型不应该包含面向具体应用的方法。

6.5 表示独立性

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

eg: list提供的操作与其实现为linked list还是array list无关

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

eg:

用户不依赖于具体实现

6.6 测试抽象数据类型

根据操作的不同进行相应的测试:

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

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

警告:由于方法之间存在依赖关系,因此如果被依赖的其他方法有错误,可能导致被测试方法的测试结果失效。因此必须保证测试方法正确。

eg:

6.7 不变量

ADT需要保持其不变量

不变量是程序的一种属性,与客户端操作无关,对于程序的每个可能的运行时状态,它都始终为真

为什么需要不变量:保持程序的“正确性”,容易发现错误

总是要假设client 有“恶意”破坏ADT不变量的行为——因此需要防御式拷贝

表示泄漏:内部的数据结构可以被外部客户看到,并且可以进行一些操作。

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

如何防止表示泄漏:

  1. 将public变为private(防止外部修改)
  2. 使用final关键字(防止内部修改)
  3. 在规约中强调说明(不是个好的方案)
  4. 最好的方法是使用不可变类型,彻底避免表示泄漏

eg:

保持不变性和避免表示泄漏,是ADT最重要的不变性

6.8 RI和AF

6.8.1 值的两个空间

表示空间R和抽象空间A

表示空间:开发者看到和使用的值

抽象空间:用户看到和使用到的值

两个空间是满射关系(每个抽象值都被某个表示值映射到,即用户的可选项一定在开发者的适用范围内),但未必是单射,也未必双射

抽象函数(AF):R和A之间的关系,即如何将R中的每一个值解释为A中的每一个值,在图中用箭头表示

RI:

另一个重要的ADT不变量(表示不变量),判断某个具体的表示是否是合法的;也可以看作所有表示值的一个子集,包含了所有合法的表示值;也可看作一个条件,描述了什么是”合法“的表示值

即RI作为一个明确的规范(不可含糊不清),操作不可越界

什么决定了AF和RI?

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

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

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

eg:

checkRep():应随时检查RI是否满足,在所有方法内都要检查

6.9 有益的可变性

有些数据类型是不可变的,即定义好之后就不可以再改变;但是,只要保证rep值继续映射到相同的抽象值,就可以自由修改rep的值,而使客户端感受不到变化。这种变化称为有益的可变性。

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

eg:

这种mutation只是改变了R值,并未改变A值

对client来说是immutable的→“AF并非单射”,从一个R值变成了另一个R值

此种可变是无害的,甚至是有益的

但这并不代表在immutable的类中就可以随意出现mutator。

6.10 记录AF、RI和Rep Exposure的安全性

6.10.1 记录AF和RI

一个很好的做法是在类中记录抽象函数和rep不变量,在rep的私有字段声明处使用注释

对于RI来说,像“所有字段都有效”这样的泛型语句是不够的,rep不变量的工作是精确解释字段值有效与否的原因。

AF 提供“表示一组字符”这样的通用解释是不够的,要精确记录AF:如何解释每一个R值

6.10.2 表示泄漏的安全声明

通过在注释中明确写出检査R的每个部分(特别是关于参数和需要返回给客户的返回值)没有将R泄漏给A,并说明没有发生表示泄漏的原因来完成。

即,不可以将具体实现泄漏给用户,用户不能知道具体实现方法,也不能对某个具体类的成员变量(属性、字段)或者实现(方法)进行修改

可能发生表示泄漏的地方:参数和返回值

方法:所有字段都是私有的,不可变的,使用防御性的拷贝等……

如何建立不变量:

构造器和生产器在创建对象时要确保不变量为true(创建不变量);

变值器和观察器在执行时必须保持不变性(保持不变量);

在每个方法return之前,用checkRep()检查不变量是否得以保持(检查不变量)。

eg:

6.11 ADT 不变量替换前提条件

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

eg:

好处:

  • 它更安全,不会出现bug,因为所需的条件(排序时没有重复)可以在一个地方强制执行,即SortedSet类型,并且因为Java静态检查起作用,防止使用根本不满足此条件的值,并在编译时出错。
  • 它更容易理解,因为它更简单,SortedSet的名称传达了程序员需要知道的内容。
  • 它更容易更改,因为SortedSet的表示现在可以更改,而无需更改任何客户端。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值