哈工大 软件构造课程 考点复习总结(第三章)

Posted standingby

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈工大 软件构造课程 考点复习总结(第三章)相关的知识,希望对你有一定的参考价值。

  1. 数据类型

Primitive types 基本数据类型(8种)

  1. Object types 对象数据类型(reference types 引用数据类型)

Short、int 、long、float、double、boolean、char、byte

如:String、BigInteger

只有值,没有ID(无法与其他值区分),不能赋值为null

immutable

有值,也有ID;

部分mutable,部分immutable

在栈中分配内存,代价低

在堆中分配内存,代价高

 

  1. 静态类型检查&动态类型检查

静态类型检查

动态类型检查

(静态类型语言 如java)

(动态类型语言 如python)

提高程序的正确性和健壮性

 

关于"类型"的检查,不考虑值(不知道运行时会是什么值)

关于"值"的检查

 

  1. Mutable & Immutable

     

    Immutable 不可变数据类型

    Immutable 不可变数据类型

    优点

    优点:安全

    优点:最少化拷贝以提高效率获得更好的性能,适合于在多个模块之间共享数据

    缺点

    缺点:频繁修改产生大量临时拷贝,需要垃圾回收·

    缺点:不安全

    其他

    一旦被创建,其值不能改变

    对于引用类型,加final限制不能改变引用

    安全地使用可变类型:局部变量(不涉及共享,且只有一个引用)

    如果有多个引用(别名),不安全

    Defensively copy 防御式拷贝:返回全新的对象

    尽可能用immutable!

 

  1. 技术分享图片

    Snapshot Diagram 画法:

  2. 基本类型:单独一个常量

    引用类型:圈住!

  3. 技术分享图片重分配:

    不可变类型(用双线椭圆),修改引用

    可变类型:修改值

  4. 技术分享图片引用:

    可变引用:单线箭头

    不可变引用:双线箭头

 

  1. Specification

  1. 作用
    1. 规约可以隔离"变化",无需通知客户端
    2. 规约可以提高代码效率
    3. 规约扮演"防火墙"角色
    4. 解耦,不需要了解具体实现
  2. 内容:只讲"能做什么",而不讲"怎么实现"
  3. Behavior equivalence 行为等价性

    是否可以相互替换

    1. 站在客户端的视角看行为等价性,不同的行为,对用户来说(根据用户需求)可能等价!
    2. 根据规约判断行为等价,两个方法符合同一个规约,则等价
  4. 规约的结构:
    1. Pre-condition
    2. Post-condition
    3. Exceptional behavior 异常行为,如果违背了前置条件,会发生什么
  5. 规约的强度与替换

    Spec变强:更放松的前置条件(前置条件更弱)+更严格的后置条件(后置条件你更强),

    两条件同时变强或变弱则无法比较。

    若规约强度S2>=S1,则可以用S2替换S1。

  6. deterministic spec & undetermined spec 确定的规约和欠定的规约
    1. 确定的规约:给定一个满足前置条件的输入,其输出唯一、明确
    2. 欠定的规约:同一个输入可以有多个输出(多次执行输出可能不同)
  7. Declarative spec & operational spec 声明式规约和操作式规约
    1. 操作式规约:如 伪代码
    2. 声明式规约:没有内部实现的描述,只有"初-终"状态

    声明式规约更有价值!

    内部实现的细节不在规约里呈现,而放在代码实现体内部注释里呈现。

  8. 技术分享图片Diagraming specification

    规约定义一个区域,该区域包含所有可能的实现方式。

    空间中的每个点表示一种方法的实现。

    对于某个具体实现,若满足规约,则落在其区域内。

    更强的规约表达为更小的区域。

  9. Quality of specification 规约质量
    1. 内聚性:spec描述的功能应单一、简单、易理解
    2. 运行结果信息丰富(可能的改变,以及返回值等),不能让客户端产生理解上的歧义
    3. 足够强(如postcondition中充分阐述各种情况)
    4. 适当弱(太强的规约,在很多特殊情况下难以达到)
    5. 在规约里使用抽象类型(在java中,经常使用interface,如Map、List,而不是HashMap、ArrayList),可以给方法的实现体和客户端更大的自由度
    6. 使用前置条件和后置条件?

      客户端不喜欢太强的pre-condition,不满足precondition的输入会导致失败

      So:不限定太强的precondition,而在postcondition中抛出异常:输入不合法,

      fail fast,避免fail大规模扩散

        是否使用前置条件取决于:

  1. check(检查参数合法性)的代价
  2. 方法的使用范围:
    1. 如果只在类内部使用(private),则可以不使用precondition,在使用该方法的各个位置进行check
    2. 如果在其他地方使用(public),则必须使用precondition,若client不满足则抛出异常

 

  1. Pre-condition and post-condition 前置条件和后置条件

Pre-condition 前置条件(requires)

Post-condition 后置条件(effects)

@param

@return @throws

对客户端的约束

在使用方法时必须满足的条件

对开发者的约束

方法结束时必须满足的条件

契约:如果前置条件满足了,后置条件必须满足

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

尽量不设计mutating的spec,否则容易引发bugs。

尽量避免使用mutable对象。

避免使用可变的全局变量。

 

ADT

  1. ADT及其四种操作

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

    ADT由操作定义,与其内部实现无关。

    可变数据类型:提供了可改变其内部数据值的操作;

    不可变数据类型:其操作不改变内部值,而构造新的对象。(没有mutators

ADT操作分类:

  1. Creators 构造器:

    不利用该类型对象产生一个新的对象

    可能实现为构造函数或静态函数(factory method)

  2. Producers 生产器:

    用已有该类型对象产生新对象

    如string.concat()(连接两个字符串,产生一个新的字符串)

  3. Observers 观察器

    如list.size()返回int(不同于原类型)

  4. Mutators 变值器(改变对象属性的方法)

    通常范围void,如果返回void,则必然意味着它改变了某些对象的内部状态

    也可能范围非空类型(如容器类的put、add方法)

     

    1. Representation Independence 表示独立性

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

 

  1. Representation exposure 表示泄漏

如client能直接接触类成员变量。

表示泄漏影响表示不变量,也影响表示独立性:无法在不影响客户端的情况下改变其内部表示。

避免方法:private、final、defensive copy

 

  1. Invariants 不变量 & Representation Invariant 表示不变量

ADT应保持其不变量在任何时候总是true;

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

作用:保持程序的"正确性",容易发现错误。

 

  1. Abstraction Function 抽象函数

技术分享图片

表示空间R

抽象空间A

值的实际实现本质

抽象表示(client看到和使用的值)

ADT实现者关注表示空间R

用户关注抽象空间A

 

 

R到A的映射

一定是满射:A中元素总有R中具体的实现

未必是单射:A中一个元素在R中可能有多重实现方式

未必是双射:R中表示不符合A中需求(如图中"abbc")

 

抽象函数AF:R和A之间映射关系的函数

AF:R->A

 

对于RI :R-> Boolean

RI:某个具体的"表示"是否合法;表示值的一个子集,包含所有合法的表示值;一个条件,描述了什么是"合法"表示值。

 

  1. 技术分享图片

    Documenting AF 、RI、Safety from Rep Exposure

选择某种特定的表示方式R

进而指定某个子集是"合法"的(RI)

并为该子集中的每个值做出"解释"(AF)

即 如何映射

 

Safety from Rep Exposure

证明代码并未对外泄露其内部表示

技术分享图片

保证不变量为true,不变量:

  1. 通过creators和producers创建
  2. 受mutators和observers保护
  3. 无表示泄漏发生

 

 

OOP

  1. Interface 接口

接口的成员变量默认用final关键字修饰,故必须有初值,可用public,default修饰,可用static修饰。

接口的方法只能被public、default、abstract、static、strictfp(严格浮点运算)修饰。

 

  1. Inheritance、override 继承和重写

Strict inheritance 严格继承:子类只能添加新方法,无法重写超类(父类)中的方法(final限制)。

考虑final修饰类、方法、属性时的不同作用。

Override 方法:具有一致的signature,复用的基本机制。

 

  1. Polymorphism ,subtyping and overloading 多态,子类型化,重载

三种多态:

  1. Ad hoc polymorphism (特殊多态)

    用于function overloading(功能重载),即重载

  2. Parametric polymorphism (参数化多态)

    泛型

  3. Subtyping (subtype polymorphism / inclusion polymorphism )(子类型多态、包含多态)

 

  1. Overloading 重载
    1. 重载条件:
      1. 方法名相同
      2. 参数列表不同,即参数类型、个数、类型顺序至少有一项不相同
      3. 返回值类型可以不同
      4. 方法的修饰符可以不同
      5. 可以抛出不同的异常
      6. 可以在类内重载,也可以在子类重载
    2. 重载是一种静态多态,静态类型检查

      (static dispatch 静态分派)并在编译阶段决定具体执行哪个方法(即对方法的调用取决于编译时声明的引用的类型)

      重写(dynamic dispatch 动态分派)则进行动态类型检查,根据运行时堆中的实例类型选择方法。

  2. Generic 泛型
    1. 通配符 <?> :只有使用泛型的时候出现,不能在定义中出现。
    2. 技术分享图片类型擦除:编译后、运行时类型擦除

      List<Integer> -> List

      注意可能引起重载编译错误。

      技术分享图片运行时不能用 instanceof 检查泛型。

    3. 不能创建泛型数组

      不能用在静态变量

      不能创建对象(不能new)

  3. Subtypes

    超类的子类型,如:ArrayList和LinkedList是List的子类型。

    子类型的规约不能弱化超类型的规约。

    1. 子类型多态:不同类型的对象可以统一处理而无需区分(不加区分地调用同样的方法等),从而隔离变化
    2. LSP(Liskov Substitution Principle) 如果S是T的子类型,那么T的对象可以被S的对象替换。
    3. Type casting 类型转换

      避免向下类型转换。

  4. Dispatch 分派

Static dispatch 静态分派

Dynamic dispatch 动态分派

将调用的名字与实际方法的名字联系起来(可能有多个)

决定具体执行哪一个操作

重载,在编译阶段即可确定执行哪个具体操作

重写,在运行时决定

   

Early/static binding

Lade/dynamic binding

绑定static、private、final方法时发生

重写父类子类的同样方法

技术分享图片

技术分享图片

 

  1. equals()

  1. 引用等价性 ==

    比较内存地址ID

    用于比较基本数据类型

  2. 对象等价性 equals()

    验证正确性:reflexive 自反性、symmetric 对称性、transitive 传递性、非空(a.equals(null) return false)

    1. hashCode()

      等价的对象必须有相同的hashCode

      Rule:重写equals时重写hashcode

     

    1. Equality of Mutable Types 可变对象的等价性

Observational equality 观察等价性

Behavioral equality 行为等价性

在不改变状态的形况下,两个mutable看起来是否一致

调用对象的任何方法都展示出一致的结果

调用observer,producer,creator

调用任何方法,包括mutator

当前情况下,看起来(如成员变量)相同

经过改变后,依然相同(只是别名引用)

 

对不可变类型,观察等价性和行为等价性完全等价。

对可变类型,往往倾向于实现严格的观察等价性。(但有时观察等价性可能导致bug,甚至破坏RI)

 

对可变类型,应当实现行为等价性,即只有指向内从空间中同样的objects才相等(即equals比较引用,如==而hashcode把引用映射为一个值)。

所以对可变类型,无需重写equals和hashcode,直接继承object。(比较引用)

若一定要判断两个可变对象是否一致,最好定义一个新的方法。

以上是关于哈工大 软件构造课程 考点复习总结(第三章)的主要内容,如果未能解决你的问题,请参考以下文章

哈工大软件构造复习——LSP原则,协变和逆变

软件构造期末复习考点总结

麻省理工18年春软件构造课程阅读15“相等”

麻省理工18年春软件构造课程阅读13“调试”

麻省理工18年春软件构造课程阅读04“代码评审”

麻省理工18年春软件构造课程阅读02“Java基础”