天天看点

软件构造复习笔记(3)

注:文章中带有 * 的标题表示往年考试中出现过相应考点

文章目录

    • 第七章 面向对象的编程(OOP)
      • 1 基本概念:对象、类、属性、方法*
      • 2 接口和枚举*
      • 4 封装和信息隐藏*
      • 5 继承和重写*
        • Overriding*
        • 抽象类
      • 6 多态、子类型、重载*
      • 9 动态分派
      • 10 一些Java中的重要对象方法*
      • 11 设计好的类*
      • 12 OOP历史
    • 第八章 ADT和OOP中的“等价性”
      • 1 等价关系*
      • 2 不可变类型的等价性
      • 3 == 和 equal()*
      • 4 实现equal()
      • 5 对象契约*
      • 6 可变类型的等价性
      • 7 自动封装和等价性
    • 第九章 面向复用的软件构造技术
      • 1 什么是软件复用
      • 2 如何度量可复用性
      • 3 可重用部件的等级和形态
      • 5 设计可复用类*
        • Liskov替代原则(LSP)*
        • 委派和组成*
      • 6 设计系统级可复用API库和框架

第七章 面向对象的编程(OOP)

1 基本概念:对象、类、属性、方法*

对象:是状态和行为的组合

状态 - 对象中所包含的数据 - 类中的实例变量

行为 - 对象所支持的行为 - 类中的实例方法

类:每个对象都属于某个类,方法定义了类型和实现

类成员变量:一个和类相关而非类的实例相关的变量 - 静态成员变量

类方法:之和类相关的方法 - 静态方法

注:本节为基础知识,需要掌握

2 接口和枚举*

接口(Interface):一个方法声明的列表,不包含方法体,可以由类实现

接口间可以继承和扩展

一个类可以实现多个接口(从而具备了多个接口中的方法)

一个接口可以有多种实现类

软件构造复习笔记(3)

接口 - 确定ADT规范,类 - 实现ADT

可以不需要接口直接使用类作为ADT,既有ADT定义也有ADT实现

但实际中更倾向于使用接口来定义变量

打破了抽象边界,接口定义中没有包含constructor,也无法保证所有实现类中都包含了同样名字的constructor。

故而,客户端需要知道该接口的某个具体实现类的名字。可以提供静态工厂方法而非构造函数来向客户端提供具体实现类。

接口中每个方法都需要在所有实现它的类中实现

可以通过default关键字修饰方法,在接口中统一实现某些功能,无需在各个类中重复实现它

以增量式的为接口增加额外的功能而不破坏已实现的类

枚举:

软件构造复习笔记(3)
软件构造复习笔记(3)

注:本节了解一下接口就行了

4 封装和信息隐藏*

信息隐藏:区分模块设计好坏的唯一最重要的因素是它对其他模块隐藏内部数据和其他实现细节的程度,设计良好的代码应该能隐藏全部实现细节

通过接口进行信息隐藏:

1.使用接口类型声明变量

2.客户端仅使用接口中定义的方法

3.客户端代码无法直接访问属性

权限修饰符:

private:只能在类内部进行使用

protected:可以在其子类及同个包中的其他类中使用

public:在所有类中都可以使用

信息隐藏策略:

1.认真设计API

2.只提供客户端需要的功能,其他所有成员都应该是private的

3.你可以在不破坏客户端的情况下将任何一个private成员修饰为public

注:本节需要了解权限修饰符的功能

5 继承和重写*

Overriding*

可重写的方法:未加final修饰的方法都可以在子类中重写,即在子类中将该方法重写实现,重写的方法有着和原方法完全相同的声明,实际执行时调用哪个方法,在运行时决定

严格继承:子类只能添加新的方法,无法重写超类中的方法

如果一个方法不能被重写,那它一定以final修饰

通产override的方法都需要在方法声明前添加@override

final作用:

1.变量 - 使变量在初始化后不能再改变取值

2.方法 - 避免子类中重写该方法

3.类 - 避免该类被继承

重写时,可以使用super()复用父类中函数的功能,并在后续代码中进行拓展

重写时,不要改变原方法的本意

抽象类

抽象方法:存在有声明,但没有实现的方法,这个方法由abstract关键字修饰

抽象类:存在至少一个抽象方法的类,该类必须由abstract关键字修饰,抽象类不能被实例化

接口可以被认为是只有抽象方法的抽象类

如果某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写。

所有子类型完全相同的操作,放在父类型中实现,子类型中无需重写。

有些子类型有而其他子类型无的操作,不要在父类型中定义和实现,而应在特定子类型中实现。

注:继承、重写是重要概念,必须掌握

6 多态、子类型、重载*

三种多态类型:

特殊多态 - 函数重载

参数化多态 - 泛型

子类型多态、包含多态 - 多个子类继承某父类

重载(Overload):多个方法具有同样的名字,但有不同的参数列表或返回值类型

价值:方便客户端使用,客户端可用不同的参数列表,调用同样的函数

重载时一种静态多态:进行静态类型检查,根据参数列表进行最佳匹配,在编译阶段时决定要具体执行哪个方法

重载规则:

1.必须有不同的参数列表

2.可以有相同/不同的返回值类型

3.可以有相同/不同的权限修饰符

4.可以有新的或更广泛的检查异常

5.可以在同一个类内重载,也可以在子类中重载

软件构造复习笔记(3)

重写(Override)则是在运行时进行动态检查,根据内存中对象的具体类型来调用对应方法

软件构造复习笔记(3)

泛型:以泛型方式定义函数和类型,以便基于运行时传递的参数工作,即允许静态类型化而不完全指定类型。

泛型编程:是一种编程风格,其中数据类型和函数用稍后指定的类型编写,然后在需要时对作为参数提供的特定类型进行实例化。

类型变量:未指定的变量类型

泛型类:类定义中包含了类型变量

泛型接口:接口定义中包含了类型变量

泛型方法:方法定义中包含了类型变量

泛型的其他性质:

软件构造复习笔记(3)

子类型:若B是A的子类型,意味着每一个B类型的变量都可以被当作A类型

B是A的子类型当且仅当B的Spec至少和A一样强,子类型的规约不能弱化父类型的规约

子类型多态:不同类型的对象可以统一的处理而无需区分

注:重载、重写应该掌握,泛型应该了解一下,子类型多态需要注意对子类型spec的要求

9 动态分派

动态分派:确定要在运行时调用的方法,即在运行时解析对已覆盖或多态方法的调用

静态分派:重载的方法使用静态绑定绑定,而重载的方法在运行时使用动态绑定绑定。

软件构造复习笔记(3)

10 一些Java中的重要对象方法*

equals方法:当两个对象等价时返回true,应满足对称、自反、传递的性质

hashCode方法:返回在哈希映射中使用的哈希代码

toString方法:返回一个可打印的字符串表示

软件构造复习笔记(3)

注:应重点关注equal方法

11 设计好的类*

不可变类的优点:简单、固有线程安全、可以自由共享、不需要防御拷贝、优秀的构建块

如何写不可变类:

1.不提供任何mutator

2.确保方法都不会被重写

3.使所有变量均被final修饰

4.使所有变量均被private修饰

5.确保任何可变类型的组成部分的安全性(避免表示泄露)

6.实现toString()、hashCode()、equals()等方法

注:需要学会怎么设计不可变的类

12 OOP历史

软件构造复习笔记(3)
软件构造复习笔记(3)

第八章 ADT和OOP中的“等价性”

1 等价关系*

ADT是对数据的抽象,体现为一组对数据的操作

抽象函数AF:内部表示->抽象表示

基于抽象函数AF定义ADT的等价操作

等价关系:自反、对称、传递

软件构造复习笔记(3)

注:需要理解什么是等价关系

2 不可变类型的等价性

如果AF映射到相同的结果,则等价

站在外部观察者的角度,对两个对象调用任何相同的操作,都会得到相同的结果,则认为两个对象是等价的,反之亦然

3 == 和 equal()*

== 运算符比较的是对象的引用,两个对象指向同一个内存空间时,则说明这两个对象具有引用等价性

equal()方法比较的是对象的内容,即对象等价性

在自定义ADT时,需要重写Object的equals()

== 通常用于对基本数据类型判断是否相等,equal()用于判断对象类型是否等价

注:需要了解如何区分两者区别

4 实现equal()

在Object中缺省equals()是在判断引用等价性,所以一般需要重写

equal方法如果接受的参数不是Object类型则实现不是override而是overload

instanceof运算符可以判断某一变量所指向内存是否属于某各类型(动态检查)

5 对象契约*

equal方法满足的契约:

1.等价关系:自反、对称、传递

2.除非对象被修改了,否则调用多次equals应有同样的结果

3.对于非null引用x,x.equals(null)应返回false

4.等价的对象,其hashCode()的结果必须相同

哈希表:

软件构造复习笔记(3)
软件构造复习笔记(3)
软件构造复习笔记(3)

等价的对象必须有相同的hashCode,但不等价的对象也可以有相同的hashCode,不够性能会变差

重载hashCode():

软件构造复习笔记(3)

注:主要关注equals()方法

6 可变类型的等价性

观察等价性:在不改变状态的情况下,两个mutable对象是否看起来一致

行为等价性:调用对象的任何方法都展示出一致的结果,可以认为就是引用等价性

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

在hashSet等类型中,其内部的可变数据类型发生改变时,hashCode会改变,导致hashSet的contains方法会判断集合中改变过的内容不属于集合

软件构造复习笔记(3)
软件构造复习笔记(3)

7 自动封装和等价性

软件构造复习笔记(3)

第九章 面向复用的软件构造技术

1 什么是软件复用

软件复用是使用现有软件组件实现或更新软件系统的过程

软件复用的两个视角:

1.面向复用编程(Creation):开发出可复用的软件

2.基于复用编程(Use):利用已有的可复用软件搭建应用系统

软件构造复习笔记(3)

复用好处:

1.降低成本和开发时间

2.经过充分测试,可靠、稳定

3.标准化,在不同应用中保持一致

2 如何度量可复用性

从以下几个角度度量:

1.复用的机会有多频繁?复用的场合有多少?

2.复用的代价有多大?

软件构造复习笔记(3)

3 可重用部件的等级和形态

最主要的复用是在代码层面,但软件构造过程中的任何实体都可能被复用 - 需求、设计/规约、数据、测试用例、文档

软件构造复习笔记(3)

白盒复用:源代码可见,可修改和拓展 - 复制已有代码到正在开发的系统,进行修改

优点:可定制化程度高

缺点:对其修改增加了软件的复杂度,且需要对其内部充分的了解

黑盒复用:源代码不可见,不能修改 - 只能通过API接口来使用,无法修改代码

优点:简单、清晰

缺点:适应性差些

复用一个类的方法:

1.继承:子类可以对父类中的属性、方法等进行复用

2.委派:在一个类中使用其他类的方法来实现自己的功能

框架:一组具体类、抽象类、及其之间的连接关系

开发者根据framework的规约,填充自己的代码进去,形成完整系统

白盒框架:通过代码层面的继承进行框架拓展

黑盒框架:通过实现特定接口/delegation进行框架拓展

5 设计可复用类*

Liskov替代原则(LSP)*

子类型多态:客户端可用统一的方式处理不同类型的对象

软件构造复习笔记(3)

在Java中的编译器强制执行的规则:

1.子类型可以增加方法,但不可以删

2.子类型中需要实现抽象类型中的所有未实现方法

3.子类型中重写的方法必须返回相同的类型或其子类型(满足协变)

4.子类型中重写的方法必须接收相同的参数类型(满足逆变)

5.子类型中重写的方法不能抛出额外的异常

LSP原则:子类方法必须相比于父类方法有着相同或更强的不变量,相同或更弱的前置条件,相同或更强的后置条件,才能使子类无条件地可以替代父类。

软件构造复习笔记(3)
软件构造复习笔记(3)

LSP是子类型关系的一个特殊定义,称为(强)行为子类型

LSP依赖于以下限制:

1.前置条件不能强化

2.后置条件不能弱化

3.不变量要保持

4.子类型方法参数:逆变

5.子类型方法返回值:协变

6.异常类型:协变

协变:随着父类型到子类型越来越具体,对于返回值类型而言,不变或变得更具体,异常的类型也是如此

逆变:随着父类型到子类型越来越具体,参数类型会相反地变化,要不变或者越来越抽象

(目前Java中这种情况会被视作overload)

数组是协变的:根据Java的子类型规则,一个类型T的数组可以容纳类型T和其子类型的变量

泛型不是协变的,ArrayList是List的子类型,但List不是类型List的泛型

泛型中的通配符:

软件构造复习笔记(3)
软件构造复习笔记(3)

委派和组成*

以排序为例,如果你的ADT需要比较大小,或者要放入Collections或Array进行排序,可实现Comparator接口构建比较器并override compare方法或者实现Comparable接口拓展ADT并override compareTo方法(不需要构建新的Comparator类,比较代码防止ADT内部)

上述例子中,Comparator属于delegation,而Comparable不属于delegation

委派:一个对象请求另一个对象的功能,委派是代码复用的一种常见形式

显式委派:将发送对象传递给接收对象

隐式委派:通过成员查找规则

软件构造复习笔记(3)

委派模式:通过运动时动态绑定,实现对其他类中代码的动态复用

委派和继承:

继承通过拓展基类来添加新操作或重写某操作

委派通过捕捉某个行为,并将其发送给另一个对象

很多设计模式都使用两者组合

如果子类只需要复用父类中的一小部分方法,则可以不需要使用继承,而是通过委派机制来实现

一个类不需要继承另一个类的全部方法时,可以通过委派机制调用部分方法,从而避免大量无用方法

软件构造复习笔记(3)

委托发生在object层面,而继承发生在class层面

组合复用原则(CRP):类应该通过其组合(通过包含实现所需功能的其他类的实例)来实现代码重用,而不是从基类或父类继承来实现多态行为和代码重用。

软件构造复习笔记(3)

CRP原则的思路:

1.使用接口定义系统必须对外展示的不同侧面的行为

2.接口之间通过extends实现行为的扩展(接口组合)

3.类implements 组合接口,从而规避了复杂的继承关系

软件构造复习笔记(3)

delegation的类型:

1.Dependency:临时性的delegation(作为方法的参数使用)

Dependency:对象需要其他对象(供应者)才能实现的临时关系

2.Assosiation:永久性的delegation(作为对象的属性使用)

Assosiation:对象类之间的持久关系,允许一个对象实例代表另一个对象实例执行操作。

3.Composition:更强的assosiation,但难以变化(属性通过内部各方法进行初始化、修改等)

Composition:是一种将简单的对象或数据类型组合成更复杂的数据类型的方法

4.Aggregation:更弱的assosiation,可动态变化(属性通过外部方法进行修改)

Aggregation:对象存在于另一个外部,在外部创建,因此它作为参数传递给解释器。

这四种类型都支持一对多的delegation

软件构造复习笔记(3)

注:本节为重点,需要理解LSP、CRP、继承、委派等概念的含义,并会分析及写相应代码

6 设计系统级可复用API库和框架

库:提供可复用功能的类和方法的集合

软件构造复习笔记(3)
软件构造复习笔记(3)
软件构造复习笔记(3)

继续阅读