哈工大软件构造-规约与ADT基础知识总结


一、规约(Specifications)

Spec是程序员在设计ADT时对自己所写方法的规约,它规定了方法应该做什么,不应该做什么。而在接下来的设计中,测试用例的编写就需要依靠Spec的描述,因为程序员所编写的代码必定是符合spec的,否则就是不合格的。同时,有了Spec的存在,客户端在使用代码时就会有所依据,好的Spec可以大大节省客户端使用自己的API时所需要的时间,并且大大降低了客户端对自己所编写的代码的误解。

1.规约的重要性

很多bug来自于双方之间的误解
不写下来,那么不同开发者的理解就可能不同
没有规约,难以定位错误
精确的规约,有助于区分责任
客户端无需阅读被调用函数的代码,只需理解spec即可

2.规约的作用

规约的核心要义:只讲“能做什么”,不讲“怎么实现”
规约可以隔离“变化”,无需通知客户端
规约也可以提高编码效率(E.g., 实现者不需要写代码确保输入的正确性,调用者的责任)
规约扮演“防火墙”角色。客户端不需要知道实现,实现者也不需要知道如何被使用。即解耦,不需了解具体实现

3.前置与后置条件

前置条件:对客户端的约束,在使用方法时必须满足的条件
后置条件:对开发者的约束,方法结束时必须满足的条件
客户端和开发者之间达成了一种契约:如果前置条件满足了,那么后置条件一定要被满足。但是,前置条件不满足,则方法可做任何事情。即“你违约在先,我自然不需要遵守承诺”

4.规约的强弱

若想要Spec变强,可以采用如下方式:
1. 更宽松的前置条件
2. 更严格的后置条件
如果是用椭圆在图中对不同的Spec强弱进行表示,则越小(包含的结果的点越少)的椭圆,其Spec越强,此处我们可以理解为:椭圆的大小代表了开发者的自由度

5.行为等价性

判断两个函数是否可以相互替换,要观察其行为等价性:行为不同,但对用户来说“是否等价”?(站在客户端的角度来说)
判断行为等价性的依据:规约
单纯的看实现代码,并不足以判定不同的实现是否是“行为等价的”
如下图两个实现
在这里插入图片描述
在val不存在于数组之中时,findfirst返回数组长度,findlast返回-1,二者皆返回一个对于数组下标来说非法的值。
在val存在于数组之中时,findfirst返回val第一次出现的下标,findlast返回val最后一次出现的下标。
两个方法的实现看上去不完全等价。

但是,如满足如下规约,两个实现是可以相互替换的。
在这里插入图片描述
原因:client调用方法时,不知道方法的具体实现,只能看到规约,并且依照规约使用方法。两个方法在规约相同的情况下,client认为二者使用的方式没有区别的,由此两个方法可以相互替换

6.改变参数的方法规约

通常来说,方法不应该改变它的参数。可以返回一个修改后的副本(如下图),而不是在原参数对象上直接进行修改。

除非在后置条件中声明过,否则不应该修改输入参数。尽量不设计mutating的spec,否则就容易引发bug。

7.判断规约强弱的实例

注意:比较后置条件时,应在前置条件相同的情况下考虑。
在这里插入图片描述
结论:前者前置条件强于后者,二者后置条件相同,则有前者规约弱于后者

在这里插入图片描述
结论:前者前置条件强于后者,前者后置条件也强于后者,则有二者规约强度无法比较。

二、抽象数据类型

1.表示独立性(Representation Independence)

client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。
通过前提条件和后置条件充分刻画了ADT的操作,spec规定了client和implementer之间的契约,明确了client知道可以依赖哪些内容,implementer知道可以安全更改的内容。

下图是一个违反RI的例子。
在这里插入图片描述
因为people的public属性,导致客户端直接调用ADT中的R空间的值。客户端与ADT有较强的耦合性,即它们的用法绑定在了一起,不利于后续的changable编程。而且,

2.AF和RI

R为表示值构成的空间:实现者看到和使用的值
A为抽象值构成的空间:client看到和使用的值

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

R和A的关系如下图所示:
在这里插入图片描述
AF(Abstraction Function)为抽象函数:R和A之间映射关系的函数,即如何将R中的每一个值解释为A中的每一个值。
AF是 满射、非单射、未必双射。如果R中的部分值并非合法的,在A中无映射值
表示不变性RI(Rep Invariant):
某个具体的“表示”是否是“合法的”
也可将RI看作:所有表示值的一个子集,包含了所有合法的表示值
也可将RI看作:一个条件,描述了什么是“合法”的表示值

RI总结:RI表示一个逻辑上的真假,可以看作是R空间的定义域

3.表示泄露

(1)表示泄露的含义:

表示泄露可以看作是,对于ADT中的mutable属性,将其引用暴露给外部,造成client在调用时有意无意的修改ADT的RI,造成一些难以追踪的bug。

(2)表示泄露发生的位置

1.在构造器中,传入的了一个外部的mutable引用
2.在mutator中,传出的了一个内部部的mutable引用

(3)表示泄漏的风险

一旦泄露,ADT内部表示可能会在程序的任何位置发生改变(而不是限制在ADT内部),从而无法确保ADT的不变量是
否能够始终保持为true。

4.有益的可变性(Beneficent mutation)

对immutable的ADT来说,它在A空间的abstract value应是不变的,但其内部表示的R空间中的取值则可以是变化的。
这种mutation只是改变了R值,并未改变A值,对client来说是immutable的 →“AF并非单射”,从一个R值变成了另一个R值
此种可变是无害的,甚至是有益的
但这并不代表在immutable的类中就可以随意出现mutator!

5.AF、RI的文档写法

在代码中以注释的形式写出AF和RI而不能在Javadoc文档中,防止被外部看到而破坏表示独立性(为了信息隐藏)。

RI:针对Rep的每一个field以及多个fields之间的关系,进行条件限定,要精确。
AF:AF:给出client看到的A值是什么,是对每一个Rep值的“数学运算”
在这里插入图片描述
如上图所示:
RI中要描写每个field的限定条件,以及field之间的关系条件;
AF中描述field如何理解成A空间的值;
最后在写上为了避免表示泄露所做的工作。


总结

1.ADT的规约里只能使用client可见的内容来撰写,包括参数、返回值、异常等。
2.如果规约里需要提及“值”,只能使用A空间中的“值”。
3.ADT的规约里也不应谈及任何内部表示的细节,以及R空间中的任何值
4.ADT的内部表示(私有属性)对外部都应严格不可见,故在代码中以注释的形式写出AF和RI而不能在Javadoc文档中,防止被外部看到而破坏表示独立性/信息隐藏

	本文主要解释了一些ADT设计中关于Spec、AF和RI的含义、内容、注意事项等内容。其细节部分相对琐碎,但是在实际应用中非常重要,因此应该牢记,并在日后有需求的时候活学活用。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值