软件构造复习(二)

数据类型与数据检验

数据类型

Java中,数据类型分为基本数据类型和对象数据类型

 基本数据类型有其对应的被包装成的对象数据类型,如int-Integer,boolean-Boolean等,一般在定义集合类时使用

静态/动态数据类型检查

Java是静态类型语言——在编译阶段进行类型检查

编译器能够导出所有表达式的类型,如a,b是int型,则a+b也是int型

静态类型检查:语法、类名/函数名、参数数目、参数类型、返回值类型 (关于"类型"的检查,不考虑值)

动态类型检查:非法的参数值、非法的返回值、越界、空指针 (关于"值"的检查)

不变性

不可变数据类型:值不可改变

引用不可变:一旦确定其指向的对象,不能再被改变

防御式拷贝

 由于Date可变,故构造Period对象时对start和end进行防御式拷贝,防止因传入的start和end变化而对Period对象产生影响

 返回时进行防御式拷贝,防止表示泄露

Snapshot diagram

基本数据类型:

 对象数据类型:用双线椭圆表示不可变对象,双线箭头表示不可变引用

 集合类

迭代器

在删除列表中的元素时,应使用itreator.remove(迭代器的删除方法),否则,被删元素后面的元素的索引发生变化而迭代器的index未改变,会发生错误。

设计规约

 规约包括功能描述、输入数据限制、返回值

行为等价性

站在客户端的角度看行为等价性、根据规约判断是否行为等价

前置条件与后置条件

前置条件:对客户端的约束,在使用方法时必须满足的条件

后置条件:对开发者的约束,方法结束时必须满足的条件

如果前置条件满足了,后置条件必须满足;前置条件不满足,则方法可以做任何事情

前置条件写在@param后,后置条件写在@return和@throws后

Java中的规约

静态类型声明是一种规约,可据此进行静态类型检查

方法前的注释也是一种规约,但需人工判定其是否满足

规约中应该写什么

规约中可以写前置条件和后置条件,但不应该写局部变量和它所在类的私有的属性

可变方法的规约

除非后置条件里声明过,否则方法内部不应该改变输入参数

规约的强弱

规约的强度S2>=S1意味着S2的前置条件比S1弱,后置条件比S1强。所以可以用S2代替S1

越强的规约,意味着implementor的自由度和责任越重,而client的责任越轻。

欠定的规约:同一个输入可以有多个输出

非确定的规约:同一个输入,多次执行时得到的输出可能不同

Diagramming specification

(椭圆)区域内的每个点表示规约的一种实现

更强的规约,表示为更小的区域 

更强的后置条件意味着实现的自由度更低了,在图中的面积更小

更弱的前置条件意味着实现时要处理更多的可能输入,实现的自由度低了,面积更小

抽象数据类型(ADT)

ADT 的特性:表示泄漏、抽象函数 AF 、表示不变量 RI

传统的类型定义:关注数据的具体表示

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

ADT是由操作定义的,与其内部实现无关!

ADT的操作类型

构造器:构造一个新对象,可能实现为构造函数或静态函数,其中声明为静态的方法通常被称为工厂方法

生产器:从一个类型的旧对象创建一个新对象(如String中concat方法)(Collections.unmodifiableList() 是 producer)

观察器:返回一个不同类型的对象(如List中的size方法)

变值器:改变对象属性的方法(如List中的add方法),通常返回void,也可能返回非空类型。不可变类型无变值器

如果一个方法既改变了对象属性,也返回了不同类型的对象,它是变值器Mutator

表示独立性

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

不变量

不变量:在任何时候总是true,例如:immutability就是一个典型的“不变量”

由ADT来负责其不变量,与client端的任何行为无关

防止表示泄露的方法:将属性设为private final、使用防御式拷贝

一个防御式拷贝的例子:在构造对象时产生的表示泄露

 在构造器中使用防御式拷贝

Rep Invariant 与 Abstraction Function

Abstraction Function

R:表示空间

A:抽象值构成的空间,client看到和使用的值

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

如:用字符串表示字符集合

 AF:抽象函数,R到A的映射

AF是满射:每个抽象值都要有一个rep value

AF未必是单射:一个抽象值可能有多种表示

AF未必是双射:不是所有的表示值都有对应的抽象值

Rep Invariant

表示不变性RI:某个具体的“表示”是否是“合法的”

RI:R->boolean

也可将RI看作:所有表示值的一个子集,包含了所有合法的表示值

也可将RI看作:一个条件,描述了什么是“合法”的表示值

选择某种特定的表示方式R,进而指定某个子集是“合法”的(RI),并为该子集中的每个值做出“解释”(AF)——即如何映射到抽象空间中的值

还是上面的用字符串表示字符集合的例子:

 其中"aa"就是一个不合法的表示,即不满足RI

不同的内部表示,需要设计不同的AF和RI

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

 有益的可变性

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

 

通过牺牲immutability的部分原则来换取“效率”和“性能”

记录AF、RI和Safe from Rep Exposure

注释中可以写什么

ADT的规约里只能使用client可见的内容来撰写,包括参数、返回值、异常等

如果规约里需要提及“值”,只能使用A空间中的“值”

ADT的规约里也不应谈及任何内部表示的细节,以及R空间中的任何值

ADT的内部表示(私有属性)对外部都应严格不可见

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

ADT不变量替代前置条件

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

posted @ 2023-05-22 16:38  YY_R  阅读(28)  评论(0)    收藏  举报