软件构造笔记3

 

目录

3.1 Data Type and Type Checking

1、编程语言的数据类型:

2、类型检查:

3、变量与不变量:

4、Snapshot diagram:

​编辑

3.2 Designing Specification:

1、规约(增强了代码的可读性):

2、规约的结构:

3、规约的评判标准:

 4、行为等价性:

3.3 Abstract Data Type (ADT)

1、ADT操作:

2、设计ADT:

3、表示独立性:

4、测试ADT:

5、AF(Abstraction Function)与RI(Rep Invariant):

Documenting the AF, RI, and Safety from Rep Exposure


3.1 Data Type and Type Checking

1、编程语言的数据类型:

  • 数据类型:一组值以及可以对其执行的操作       
  • 变量:用特定数据类型定义,可存储满足类型约束的值
  • 例:Java中的数据类型包括:基本数据类型和对象数据类型

在这里插入图片描述

对象数据类型在OOP中起着重大的作用,基于其继承机制,OOP编程时可以更好地复用代码

将基本类型包装为对象类型,通常是在定义容器类型的时候使用它们(容器类型操作的元素要求是对象类型,所以需要对基本数据进行包装,转换为对象类型)

其他情况下尽量避免使用(有可能会降低性能)。基本类型和对象类型之间一般可以自动转换。

2、类型检查:

  • 静态类型检查:在编译阶段进行,避免了将错误带入到运行阶段,可以提高程序的正确性/健壮性,注意检查的是“类型”,不考虑值。
  • 动态类型检查:在运行阶段进行,是关于“值”的检查
  • 动态类型检查的语言也会进行静态检查(除了类型外的其他语法错误)
    • 例如:类名/函数名错误,参数数目错误,参数类型错误,返回值类型错误等

3、变量与不变量:

  • 改变一个变量:将变量指向另一个存储空间
  • 改变一个变量的值:将该变量当前指向的存储空间中写入一个新的值
  • 使用不可变类型对其频繁修改会产生大量的临时拷贝
    • 更便于读懂程序,更易满足规约
  • 可变类型因为最少会拷贝,可以提高效率
    • 使用可变类型,可获得更好的性能,也适合于在多个模块之间共享数据
  • 注意:
    final类无法派生子类
    final变量无法改变值/引用
    final方法无法被子类重写
  • 不变对象:一旦被创建,始终指向同一个值 / 引用
    可变对象:拥有方法可以修改自己的值 / 引用
  • 关于不可变类型的注意点:一般来说 immutable类的rep是不可更改的,但这也并非是绝对的。只要保证更改后它所表达的抽象含义没有更改, 它的数据域就可以被更改。这种更改一般在 observer等方法中完成,ADT中还是不能加入 mutator方法。

4、Snapshot diagram:

用于形象化表述程序运行时的内部状态:

在这里插入图片描述

 Object types:

单线圈,表示的是可变类型:

在这里插入图片描述

 双线圈表示的是不可变类型

在这里插入图片描述

可变的引用,单线箭头,见下图中的age

不可变的引用,双线箭头,针对fianl关键字限定了的变量:

在这里插入图片描述

 引用是不可变的,但指向的值却可以是改变的
可变的引用也可指向不可变的值。

3.2 Designing Specification:

1、规约(增强了代码的可读性):

只讲能做什么,不讲怎么实现

需要规约的原因:

  • 很多bug来自于双方之间的误解;
  • 没有规约,容易定位错误等
  • 与注释不同的是,注释是给编译器和编程人员读,避免错误的开发,规约是程序员对自己写的方法的约束,是给客户端读的,让客户更容易理解代码的使用方法等。

规约的优点:

  • 精确的规约,有助于区分责任;
  • 客户端无需阅读被调用函数的代码,只需理解spec即可

2、规约的结构:

  • precondition前置条件:对客户端的约束,在使用方法时必须满足的条件
    • 使用@param annotation说明每个参数的前置条件
  • postcondition后置条件:对开发者的约束,方法结束时必须满足的条件
    • 使用@return annotation说明后置条件
    • 使用@throws annotation说明出现异常的时候会发生什么
  • 在方法声明中使用static等关键字声明,可据此进行静态类型检查
  • 前置条件满足,则后置条件必须满足;前置条件不满足(说明有bug),则方法可以做任何事情
  • 方法前的注释也是一种规约,但需人工判定其是否满足
  • 例:

注意:方法不应该改变输入参数的取值,如果改了,则必须在spec中做出说明。所以不推荐使用mutable的对象。另外,我们无法强迫类的实现体和客户端不保存可变变量的“别名”,因此,如果直接返回本来的mutable对象,客户端可能修改它的值造成内部实现的错误,同样,如果返回了的是原来mutable对象的拷贝,虽然内部不用再担心客户端的更改影响到自己,但客户端无法知道内部是否保留了被返回的拷贝的别名,因此双方无法完全的信任彼此,故而不推荐使用mutable类作为返回值类型。

3、规约的评判标准:

一个好的spec应该是内聚的即只做一件事,并且有足够的信息不让客户端产生歧义,强度也要适中,以防过强难以开发&过弱使客户端难以使用。

在这里插入图片描述

 4、行为等价性:

判断两个方法是否符合同一个规约,若符合,则他们等价

3.3 Abstract Data Type (ADT)

抽象数据类型与表示独立性:能够分离程序中数据结构的形式和对其使用的方式。

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

1、ADT操作:

  • Creators构造器:用于使用 new 关键字创建一个新的对象。还有一种方法是静态方法,如Arrays.asList()String.valueOf(Object Obj)等。

  • Producers生产器:用于使用一个存在的对象产生一个新的对象,例如String.concat()就是使用已存在的字符串构造出一个新的对象,而且不会改动原先存在的对象。

  • Observers观察器:不对数据做任何改动,只是查看一个已经存在的对象的各个值,如List.size()、所有的getter方法等。

  • Mutators变值器:用于改变对象属性的方法,如List.add()。mutator通常返回void,因为它不需要对外界做出反应,只是对ADT的数据域做了更改;mutator也可能返回非空,比如返回boolean表示修改是否成功等。

  • 例:

2、设计ADT:

设计一个好的ADT需要靠开发者的经验来设计它的操作的spec,设计一个ADT要遵循下面三个原则:

(1)设计简洁一致的操作(复杂的操作要通过简单操作的组合实现,操作的行为应该是内聚的)

(2)要足以支持client所需要的对数据的所有操作,且用操作的难度要低

  • 判断方法:对象每个需要被访问到的属性是否都能被访问到

(3)要么抽象要么具体,不要混合——要么针对抽象设计,要么针对具体应用的设计。

实现一个ADT的三个部分:specification、representation、implementation

3、表示独立性:

客户使用ADT时无需考虑其内部如何实现。ADT内部表示的变化不应影响外部spec和客户端。client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。其目的一个是为了便于未来的升级和维护,当内部发生变化的时候不会影响到client。

4、测试ADT:

因为测试相当于client使用ADT,所以它也不能直接访问ADT内部的数据域,所以只能调用其他方法去测试被测试的方法。

  • 针对creator:构造对象之后,用observer去观察是否正确

  • 针对observer:用其他三类方法构造对象,然后调用被测observer,判断观察结果是否正确

  • 针对producer:produce新对象之后,用observer判断结果是否正确

5、AF(Abstraction Function)与RI(Rep Invariant):

Invariants:不变量,与程序运行无关,在任何时候都应该满足的一些条件(由ADT来负责)

两个空间 R (表示空间)和 A(抽象空间):R空间是ADT的内部表示的空间,A空间是ADT能够表示的存在于实际当中的对象。ADT的开发者关注的是R空间,client关注的是A空间。

Abstraction Function:从R空间到A空间存在一个映射,这个映射是一个满射,这个映射将R中的每一个值解释为A中的一个值。这个解释函数就是AF

Rep Invariant:这是一个集合,是R空间所有值的子集,它包含了所有合法的表示值,而只有满足RI的值,才是合法值,才会在A空间内有值与其对应。

相同的R空间有肯能会有不同的RI。

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

表示不变性RI:某个具体的“表示”是否是“合法的”
也可将RI看作:所有表示值的一个子集,包含了所有合法的表示值
也可将RI看作:一个条件,描述了什么是“合法”的表示值

checkRep():用于随时检查RI是否满足。使用assert检查RI,在所有的方法最好都加入调用这个检查方法。checkRep()在检查时有可能耗费大量的时间影响性能,所以只需要在开发阶段保留这部分。

表示泄露:client可以拿到数据域的本身或别名。一旦表示泄露,client就有可能无意间改动数据,而如果在设计中,要求一个ADT是Immutable的,而如果它出现了表示泄露,就有可能违反Immutable的原则。

Documenting the AF, RI, and Safety from Rep Exposure

在代码中用注释的形式记录AF(如何解释每个R值)和RI(rep中哪些数据是有效的)。

在代码中用注释记录表示泄露的安全声明,证明代码并未对外泄露其内部表示。比如传入和传出都使用了防御式拷贝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值