第5章 规约(specification)
1.编程语言中的函数&方法
参数类型:静态检查是否匹配;
返回值类型:也是;
方法:
- 客户端使用方法而无需知道内部实现,是一种抽象;
- 包括规约spec和实现体implementation;
2.规约Specification:面向沟通编程
1)编程中的文档
文档假设assumption
- 定义变量的数据类型
- 使用final定义设计决策
面向沟通编程
-
为什么写“假设”——防止自己记不住&别人看不懂;
-
两个目标:
- 代码体现的设计决策,让编译器看懂;
- 注释写明设计决策:让人看懂;
2)(方法的)规约
是程序与客户端达成的一致,为双方确定责任,调用时都要遵守;
3)行为等价性
- 以客户端视角看行为等价性——是否可以互相替换;
- 根据规约判断是否行为等价——规约相同,等价;
4)规约结构:前置条件和后置条件
规约结构
- 规约的内容:
- 前置条件,关键字requires,使用@annotation说明
- 后置条件,关键字effects,使用@return说明
- 例外行为:违反规约后的结果
- 前置条件:对客户端的约束,使用方法时需要满足的条件;
- 后置条件:对开发者的约束,方法结束后满足的条件;
- 契约:如果前置条件满足,后置条件必须满足;
- 而前置条件不满足,方法可以做任何事情;
java的规约
- 静态类型声明是一种规约;
- 方法前的注释也是一种规约
3.设计规约
规约的强度(更强):
- 前置条件更弱;
- 后置条件更强;
spec变强=更放松的前置条件+更严格的后置条件;
第6章 ADT 抽象数据型
0 抽象数据类型和表示独立性:
设计抽象数据结构,通过封装避免客户端client获取数据类型的内部表示,实现implementer对于用户client不可见,防止bug。
AF:abstraction functions,
RI:representation invariants
1 Abstraction and user-defined types
类型:
- built-in
- user-defined
数据抽象:
- 包括数据、数据上能执行的操作;
- 传统类型更注重数据本身(比如内置类型),而抽象类型(各种java class)更注重数据上的操作;
抽象数据类型是由它的操作定义的
- e.g.提到列表,不是指它的实现是链表还是数组,而是说有着列表能够实现的操作:get(),size(),etc;
2 类型和操作的分类可变类型和不可变类型:
- mutable/可变类型:对象提供了可以改变其内部数据的值的操作;
改变 一般指改变引用,改变值 指改变引用/指向的数据
- immutable/不变数据类型:操作不能改变内部值,而是构造新的对象(或者什么都不改变)
四类ADT操作:
- creators 构造器
- 实现:构造函数constructor(构造实例)或静态方法(不受实例影响)
- producers 生产器
- 使用一个旧对象,返回一个新对象;//已有对象“生”出新对象
- e.g. String.concat();
- observers 观察器
- 接受一个对象,返回一个非对象类型;
- e.g. size()
- mutators 变值器
- 能够改变对象属性(内部数据)的方法;
- e.g. add();
构造器的特点
- 一个creator或者是构造函数constructor,或者是一个静态函数;
- 通过静态函数实现的creator通常称为factory method(工厂方法);
- 比如String.valueof(Object obj)是通过工厂方法构造的String;
除了构造函数本身以外,直接使用值创造对象的都是构造器
mutator的特点
- 变值器通常返回void;
- 也可能返回非空类型,比如栈的弹栈&取值
3 例子
Integer,String,List
4 设计一个抽象类型
- 操作简单一致;
- 提供的操作足以满足用户需求,同时足够简单;
5 表示独立性 RI Representation Indepdence
- 用户使用ADT时不需要考虑内部实现,内部实现的变化不影响外部spec(规约)和客户端;
- 换言之,RI就是说,程序员为客户端提供ADT的各种操作,然后只要坚持操作的规约,不改变其输入和输出(格式),只修改内部实现而不影响客户端使用;
不同的实现为客户端提供相同的功能
ADT的snapshot画法(参考第四章内容)
6 测试
相当于黑盒测试(因为内部数据对于用户不可见)
- 测试creators、producers、mutators:调用observes观察这些操作的结果是否满足spec;
spec是设计规约,程序和客户端都要遵守的要求
- 测试observers:调用其他三种方法或者改变对象,看结果是否符合预期;
7 不变量 Invariants
- ADT的不变量正如其名,不会发生改变,任何时候都是true
- 比如immutability
- 由ADT/设计者负责,客户端无关
8 Rep Invariant and Abstract Function
两种 值空间
- R:表示空间(变量空间?(数据的)代表空间?)
- A:抽象空间(数值、数据空间?
R、A构成的图,R到A的映射,R指向A
- R到A为满射
- 未必单射(所以未必双射)
抽象函数(Abstraction Function,AF):R和A之间映射关系的函数,即如何解释R为A中的一个值(为R中的一个对象分配A中的一个值)
Rep Invariant,PI:
-
一个PI将R中的值映射到布尔值T/F;
-
对于一个rep value(R中的值,表示值)r,RI(r)为真当且仅当r被AF映射;
-
换言之,RI表示了一个给定的rep value是否是规定好的、(可)“合法”(使用)的;
-
RI的值取决于AF,算是AF的一个observer?HH
AF、RI如何决定
- 抽象值空间本身不能决定AF、RI,不同的内部表示可以在相同的abstract value space上设计不同的AF、RI;
根据RI、AF设计ADT
- 首先选择R和A;
- 由RI确定合法的表示值;
- 由AF解释合法的表示值;
- 做出对R、A、RI、AF的选择,要写进代码里(注释?)使之后使用者能意识到这些表示的意义;
随时检测RI是否为真
- 在所有改变rep/表示值的方法内都要检查(改变后是否满足RI);
- Observer方法(类似const、只读)可以不检查
9.Beneficent mutation 有益的可变性
- 对于抽象值,A空间里的值应该永远不被改变;
- 但是表示值可以作为mutable的,只要保持映射向相同的抽象值;
- 对于immutable的ADT,在A空间中的抽象值应该保持不变;但其内部表示的R空间中的取值可以变;
- e.g.对于除法运算,被除数和除数同时乘-1,对结果无影响;
10.Documenting the AF、RI,Safety from Rep Exposure 记录AF、RI,暴露表示值的安全性
记录AF、RI
- 在代码中用注释记录AF、RI;
- RI:表示值中的field如何才有效;
- AF:如何解释R值;
ADT spec包含什么:
- ADT的规约只能用client可见的内容来撰写,包括参数、返回值、异常;
- 如果规约中需要提到“值”,只能使用A空间里的;
11.ADT不变量替换条件
用不变量取代复杂的precondition,相当于把前提封装到ADT内部;