3. 面向对象编程基础

本章讨论的主题是面向对象编程的基础。其中一些是独立于语言的,另一些是特定于C#、Java或Scala的。这些主题,也许除了最后两个,应该在本课程的CS 1/2先决条件链中涵盖。

待处理

更新所有示例和参考资料-同时,请访问 here

3.1. 引用语义与值语义

  • 值语义: 变量直接包含值。

  • 引用语义: 变量包含引用(指向)对象的地址。

3.1.2. 参考文献

  • Java

  • C# (另见 Effective C# 第6项)

3.2. 平等与同一性

  • 平等: 两个(可能)不同的物体是等价的吗?

  • 身份: 两个引用是否引用相同的对象?

  • 平等和身份有什么关系?

  • 调和值和引用语义:解释为地址相等的对象标识。

3.2.2. 参考文献

3.3. 参数多态性(泛型)

熟悉数据结构课程:

  • 没有泛型: Stack (指物体);打字松散

  • 没有泛型: IntStackStringStackBookStack ;严格键入,但有大量重复的样板代码

  • 使用泛型: Stack<Int>Stack<String>Stack<Book> ;严格打字,无代码重复

相对容易使用,在抽象的设计中正确结合可能是一件很有挑战性的事情。

3.3.2. 参考文献

3.4. 类和接口之间的关系

这些是概念之间的常见关系,它们是 part of UML's class diagrams

3.4.1. 类/接口级关系

这些关系在类和/或接口之间,因此它们 不能 在运行时更改。

从强到弱:

  • is-arealizes-a :泛化/专门化(子类型)

  • uses-a :从属关系

3.4.2. 实例级关系

这些关系在实例之间,因此它们 can 在运行时更改。

从强到弱:

  • owns-a :合成

  • part-of :聚合

  • has-a 或其他特定关系:协会

3.4.3. 示例

UML class diagram representing a taxonomy of vehicles

表示车辆分类的UML类图。

3.5. 类接口连续体

  • 混凝土班级 (C++、C#、Java、Scala):可以实例化。所有指定的方法都已完全实现。

  • 抽象类 (C++、C#、Java、Scala):无法实例化。某些或全部指定的方法未实现。一个类不能扩展多个抽象类。

  • 特性 (仅限Scala):不能直接实例化。某些或全部指定的方法未实现。一个类或特征可以扩展零个或多个特征,并且成员查找会根据特征顺序自动消除歧义(请参见 traitsmixins 有关详细信息,请参见)。

  • 接口 (仅限Java、C#):完全抽象类的限制大小写仅用于规范目的。没有实现任何指定的方法,也没有实例变量。

与单一责任和界面分离原则有关。

3.5.2. 参考文献

3.6. 子类型化与子类化/继承

  • Subtyping 允许用更具体的对象替换更一般的对象,例如,当作为参数传递或赋值给变量时。

  • Inheritance 是子类重用超类中的状态和行为的机制。

    • 继承方法和字段

    • 添加字段

    • 添加或替换/优化方法

  • 从超类继承可以实现弱句法子类型。(在某些语言中,此关系可以是公共的,也可以是非公共的。)

  • 这个 Liskov Substitution Principle (LSP) 定义强语义(行为)子类型。

  • 实现或扩展接口还会启用语法子类型(以及语义子类型,因为接口没有行为)。扩展特征还支持语法子类型。

3.6.2. 参考文献

3.7. 亚型多态性:静电与动态型

  • 静电类型: 变量的声明类型。

  • 动态类型: 变量引用的对象的实际类型。

  • 动态方法绑定: x.f(a1, a2, ...) 。两个步骤:

    1. 验证接收方x是否支持基于静电类型的方法f。

    2. 从dynamic type开始向上搜索要调用的f版本,直到找到为止。

  • 静电和变量的动态类型有什么关系?

  • 如果步骤1成功,步骤2是否总是成功?

  • 选角: 将对象视为不同的静电类型。三种不同情况: 向下投射 - 向上投射 - 交叉广播

  • 超载与超载。- @Override/override Java和Scala中的正确性

3.7.2. 参考文献

3.8. 做好java.lang.Object/System.Object的后代

通常需要类提供以下方法(这些特定的方法用于Java):

  • toString (用于以有意义的方式显示实例)

  • equals (如果一个实例可以在包含其他实例的等价类中)

  • hashCode (同上)

  • compareTo (如果订购了实例)

  • clone (如果实例是可变的)

  • close (如果实例是 closeable resources )

也与利斯科夫替代原理有关。

3.8.2. 参考文献

3.9. 在复合模式的上下文中克隆

通常,克隆允许您复制对象。Java中的克隆方法类似于C++中的复制构造函数,但与复制构造函数不同,它是一种普通方法。一旦拥有了原始对象及其克隆,就可以单独修改每个对象。因此,只有当对象是可变的时,才需要克隆。

克隆模拟了现实生活中的情况,你可以构建某样东西的原型,比如一辆汽车或一件家具,一旦你喜欢了它,你就可以想克隆多少次就克隆多少次。这些都是组合,需要进行深度(递归)克隆。

再举一个例子,假设有一个停车场,上面有一系列可以访问它的汽车。要建造另一个车库来满足不断增长的需求,您可以克隆车库和客户访问列表。但是(物理的)汽车不应该被克隆。那是因为车库不是由汽车组成的。

正如我们所看到的,聚合和组合在概念上的区别对关系的实现有着重要的影响。如果为True,则这两个关系在Java中都表示为引用。但是,组合通常需要深度克隆(如果支持克隆),其中每个父级负责克隆其自己的状态并递归地克隆其子级。

如上所述,如果您的对象是不可变的,则根本不需要支持克隆,因为您无论如何都无法区分原始对象和克隆对象。

3.9.1. 参考文献

3.10. 包/命名空间

  • 用于对相关或协作类进行分组的机制(参见默认包级成员访问)。

  • 在Java中,实现为从完全限定类名到文件系统的映射。在Scala中,这要宽松得多。

  • 此外,在Java中,每个 公共的 类必须位于名称与类名称匹配的单独文件中。

3.10.1. 示例

3.10.2. 参考文献

3.11. 成员访问权限

  • 公共的

  • 受保护

  • 默认(程序包)

  • 私人

涉及到信息隐藏和开闭原理。

3.11.1. 参考文献