一、抽象数据类型
1.ADT相关概念
抽象数据类型是一种不同于编程语言所提供的基本数据类型,用户自己定义的数据类型,例如类等。本质上ADT是由操作定义的,与其内部如何实现无关!
可以讲ADT分为可变的与不可变的数据类型:
可变类型的对象:提供了可改变其内部数据的值的操作,如StringBulider
不可变数据类型: 其操作不可改变内部值,而是构造新的对象 ,如String
对于ADT的操作,可以大致分为四类:
Creators:构造器(从无到有) t* → T 可能实现为构造函数或静态函数
Producers:生产器(从有到新) T+, t* → T 如 String.concat()
Observers:观察器 T+, t* → t 如 List.size()
Mutators:变值器,改变对象属性的方法 T+, t* → void | t | T 如果返回值为void,则必然意味着它改变了对象的某些内部状态
2.设计ADT
1.设计简洁、一致的操作
2.要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低
3.要么抽象、要么具体,不要混合 ---要么针对抽象设计,要么针对具体应用的设计
二、表示独立性
1.表示独立性
我们称类中的具体属性为该抽象数据类型的表示,表示独立性的核心含义就是要达到使用效果与表示相独立,互不依赖。client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。
2.测试ADT
1.测试creators, producers, and mutators:调用observers来观察这些operations的结果是否满足spec;
2. 测试observers:调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确。
注意:测试时,对于自定义ADT需要重写equals和hashcode,后续会有讲解。
三、不变量
1.不变量
不变量就是程序在任何时候总是true的性质,由ADT 来负责其不变量,与client端的任何行为无关,可以保持程序的“正确性”,容易发现错误。
我们需要注意,防止表示泄漏,不仅影响不变量,也影响了表示独立性。当代价很高时可以在规约中做出相应规定,实际上最好的办法还是使用不可变数据类型。
2.表示不变量与抽象函数
我们可以将一个程序抽象成一个由实现者看到和使用的值R到使用者看到和使用的值A的一个映射:AF : R → A,其中R叫作表示空间,A叫作抽象空间。显然R->A一定是满射但未必单射、未必双射。
表示不变性RI:某个具体的“表示”是否是“合法的”
也可将RI看作是所有表示值的一个子集,包含了所有合法的表示值
也可将RI看作是一个条件,描述了什么是“合法”的表示值
总之,RI作用就是描述了R中合法的表示
同一个ADT,可以有多种表示;不同的内部表示,需要设计不同的AF和RI;同样的表示空间R,可以有不同的RI; 即使是同样的R、同样的RI,也可能有不同的AF,即“解释不同”。
所以我们在设计ADT时需要:
(1) 选择R和A;
(2) RI ---合法的表示值;
(3) 如何解释合法的表示值 ---映射AF;
(4) 做出具体的解释:每个rep value如何映射到abstract value,而且要把这种选择和解释明确写到代码当中,以注释的形式(注意,不能以规约的形式,因为规约是可以暴露给客户端的,如果把RI和AF暴露给客户端,这也是一种表示泄露);
(5) 在所有可能改变rep的方法内都要检查是否破坏了表示不变量,可以用checkRep函数的形式;
(6) 构造器和生产器在创建对象时要确保不变量为true ;变值器和观察器在执行时必须保持不变性。