软件构造第二阶段课程总结(上)

依据三个实验的知识点需求,可以将课程划分为三个阶段。

第二阶段则是Lectur4-Lecture8:抽象数据类型和面向对象编程。

1、数据类型与类型检验

1.1、静态数据类型检验vs.动态类型数据检验

静态类型语言:所有变量的类型在编译时是已知的,因此编译器也可以推断所有表达式的类型。

动态类型语言:在运行阶段进行类型检查。

静态类型检查:在程序开始运行前bug就被自动查找了。关于“类型”的检查,不考虑“值”。

动态类型检查:在程序运行时bug会被自动查找。关于“值”的检查。

静态类性检查>动态类型检查>无检查。

1.2、数据类型的可变性与不可变性

变量指向的地址和该地址中存在的值是讨论可变性与不可变性的两个维度。

不变性是一种重要的设计原则,不变数据类型一旦被创建后其值就不可更改。如果希望该对象的引用也不能更改,在声明前加上final即可。

如果编译器无法确定final变量不会改变,就提示错误,这也是静态类型检查的一部分。final类无法派生子类,final方法无法被子类重写。

使用不可变类型,虽然具有更高的安全性,但是对其频繁进行修改会产生大量的临时拷贝。而使用可变类型则可以最少拷贝以提高效率,也适合在多个模块之间传输数据,但相当于全局变量,具有风险。在选择可变类型和不可变类型时需要权衡不同的质量属性。

1.3、运行时的代码瞬间快照图

代码的程序快照图用于描述程序运行时的内部状态,便于程序员之间的交流,便于刻画各类变量随时间变化,便于解释设计思路。

基本类型的值用弧线箭头以及该变量的值来表示:

 对象类型的值用弧线箭头以及圆圈来表示:

 不可变对象用双椭圆:

 不可变引用用双箭头:

 

2、规约设计

“方法”是程序的“积木”,可以独立开发、测试、复用。使用方法的客户端,无需了解方法内部具体如何工作,而是通过规约来了解如何从外部直接使用方法——“抽象”。

2.1、规约:用于交流

规约是团队工作的关键,在没有规约的情况下很难实现一个具体的方法。规约也是一种契约,使得程序和客户端达成一致。它说明了方法和调用方的责任,定义了正确的实施意味着什么。精确的规约有助于区分责任,客户端无需阅读调用函数的代码,只需要读懂规约即可。因此,规约也可以隔离函数实现的“变化”,不需要通知客户端。他扮演了实现与客户端之间的防火墙这一角色。

根据规约,可以判断两个方法的行为是否等价。如果都符合同一个规约,那么就等价。用规约来判断两个方法是否等价要比直接看代码方便得多。

规约分为前置条件和后置条件。前置条件主要讲述了使用方法需要满足的条件,是对客户端的约束。后置条件主要讲述了方法结束时满足的条件和效果,是对实现者的约束。如果前置条件满足了,后置条件必须满足。前置条件不满足,后置条件则也可以不满足。规约当中,一般用@param来声明参数条件,@return和@throws来声明返回条件。前置条件可以写入参数条件中,后置条件可以写入返回条件中。

规约写好后,可以通过黑盒测试来确认方法是否满足了规约。

2.2、设计规约

首先,我们可以将规约分类。有强度、确定性、陈述性三个维度。可以用于判断哪个规约更好。

如果规约S2与S1相比,前置条件更弱,后置条件更强,那么S2就是更强的规约。对于用户来说更容易使用,结果更加可控。强大的规约是对他人的宽容,对自己的严格。可以用韦恩图来理解,更强的规约所规定的方法实现是更弱的规约的方法实现的子集。

 

确定的规约:给定一个满足前置条件的输入,其输出是唯一的、明确的。欠定的规约:同一个输入可以有不同的输出,通常有确定的实现。

操作式规约:例如伪代码。声明式规约:没有内部实现的描述,往往更有价值。

那么,如何设计更好的规约呢?

一方面,客户端需要用的舒服;另一方面,开发者编着舒服。

规约描述的功能应该单一、简单、易理解。需要信息丰富,不能让客户端产生理解歧义。在规约里使用抽象类型,可以给方法的实现体与客户端更大的自由度。规约是否使用前置条件应该衡量检查输入合法性的代价与给用户带来的便利程度。

3、抽象数据类型

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

数据分为可变类型和不可变类型,前者提供了可改变其内部数据的方法,后者的操作不改变内部值,而是构造新的对象。操作可分为构造器(可以实现为构造函数,也可以实现为静态函数)、生产器、观察器、变值器(返回值往往是void)。

3.1、设计抽象数据类型

规则一:设计简洁、一致的操作。

每个操作都应该有明确的目标,并且应该有一个连贯的行为,而不是一大堆特殊情况。

规则二:要足够支持客户端对数据所做的所有操作的需要,且用操作满足客户端的需求的难度要低。

规则三:要么抽象,要么具体,不要混合——要么针对抽象设计,要么针对具体应用的设计。

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

测试ADT:测试不可避免的会互相影响,如果被依赖的其他地方有错误,可能导致被测试方法的测试结果失效。

3.2、表示不变量和抽象函数

我们将抽象函数具体实现时用到的可能的数据的集合称为表示空间R,客户端看到的和使用的值称为抽象空间A。那么ADT的开发者关注的则是R,用户关注的则是A。R中部分值并非合法的,表示不变量RI可以筛选出满足条件的合法的值,而抽象函数AF则可以从这些值中产生一个从R到A的满射。一般情况下RI和AF就在rep本身声明的旁边。

设计ADT,则需要选择R和A,RI——合法的标示值,AF——如何解释合法的标示值,并且要把这种选择和解释明确写在代码中。

这时对于不变型数据的定义可以更加完善了:抽象值应该永远不被改变。只要rep继续映射到相同的抽象值,实现就可以自由的修改rep值,用户就看不到了。这样可以通过牺牲不变型的部分原则来换取效率和性能。同时,ADT的方法前的规约只能用客户端能看见的内容来撰写,当提到“值”时,必须是抽象值。

用ADT不变量取代了复杂的precondition,相当于规约的precondition被封装到了ADT内部。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值