3. 面向对象编程基础¶
本章讨论的主题是面向对象编程的基础。其中一些是独立于语言的,另一些是特定于C#、Java或Scala的。这些主题,也许除了最后两个,应该在本课程的CS 1/2先决条件链中涵盖。
待处理
更新所有示例和参考资料-同时,请访问 here
3.1. 引用语义与值语义¶
值语义: 变量直接包含值。
引用语义: 变量包含引用(指向)对象的地址。
3.2. 平等与同一性¶
平等: 两个(可能)不同的物体是等价的吗?
身份: 两个引用是否引用相同的对象?
平等和身份有什么关系?
调和值和引用语义:解释为地址相等的对象标识。
3.3. 参数多态性(泛型)¶
熟悉数据结构课程:
没有泛型:
Stack
(指物体);打字松散没有泛型:
IntStack
,StringStack
,BookStack
;严格键入,但有大量重复的样板代码使用泛型:
Stack<Int>
,Stack<String>
,Stack<Book>
;严格打字,无代码重复
相对容易使用,在抽象的设计中正确结合可能是一件很有挑战性的事情。
3.3.1. 示例¶
3.3.2. 参考文献¶
Java (另见 this tutorial 和 here 有关高级问题的信息)
3.4. 类和接口之间的关系¶
这些是概念之间的常见关系,它们是 part of UML's class diagrams 。
3.5. 类接口连续体¶
混凝土班级 (C++、C#、Java、Scala):可以实例化。所有指定的方法都已完全实现。
抽象类 (C++、C#、Java、Scala):无法实例化。某些或全部指定的方法未实现。一个类不能扩展多个抽象类。
特性 (仅限Scala):不能直接实例化。某些或全部指定的方法未实现。一个类或特征可以扩展零个或多个特征,并且成员查找会根据特征顺序自动消除歧义(请参见 traits 和 mixins 有关详细信息,请参见)。
接口 (仅限Java、C#):完全抽象类的限制大小写仅用于规范目的。没有实现任何指定的方法,也没有实例变量。
与单一责任和界面分离原则有关。
3.5.1. 示例¶
3.6. 子类型化与子类化/继承¶
Subtyping 允许用更具体的对象替换更一般的对象,例如,当作为参数传递或赋值给变量时。
Inheritance 是子类重用超类中的状态和行为的机制。
继承方法和字段
添加字段
添加或替换/优化方法
从超类继承可以实现弱句法子类型。(在某些语言中,此关系可以是公共的,也可以是非公共的。)
这个 Liskov Substitution Principle (LSP) 定义强语义(行为)子类型。
实现或扩展接口还会启用语法子类型(以及语义子类型,因为接口没有行为)。扩展特征还支持语法子类型。
3.6.1. 示例¶
3.6.2. 参考文献¶
Java (另见 these pitfalls )
C# (另见 Effective C# 第22项)
3.7. 亚型多态性:静电与动态型¶
静电类型: 变量的声明类型。
动态类型: 变量引用的对象的实际类型。
动态方法绑定:
x.f(a1, a2, ...)
。两个步骤:验证接收方x是否支持基于静电类型的方法f。
从dynamic type开始向上搜索要调用的f版本,直到找到为止。
静电和变量的动态类型有什么关系?
如果步骤1成功,步骤2是否总是成功?
选角: 将对象视为不同的静电类型。三种不同情况: 向下投射 - 向上投射 - 交叉广播
超载与超载。-
@Override
/override
Java和Scala中的正确性
3.7.1. 示例¶
3.8. 做好java.lang.Object/System.Object的后代¶
通常需要类提供以下方法(这些特定的方法用于Java):
toString
(用于以有意义的方式显示实例)equals
(如果一个实例可以在包含其他实例的等价类中)hashCode
(同上)compareTo
(如果订购了实例)clone
(如果实例是可变的)close
(如果实例是 closeable resources )
也与利斯科夫替代原理有关。
3.9. 在复合模式的上下文中克隆¶
通常,克隆允许您复制对象。Java中的克隆方法类似于C++中的复制构造函数,但与复制构造函数不同,它是一种普通方法。一旦拥有了原始对象及其克隆,就可以单独修改每个对象。因此,只有当对象是可变的时,才需要克隆。
克隆模拟了现实生活中的情况,你可以构建某样东西的原型,比如一辆汽车或一件家具,一旦你喜欢了它,你就可以想克隆多少次就克隆多少次。这些都是组合,需要进行深度(递归)克隆。
再举一个例子,假设有一个停车场,上面有一系列可以访问它的汽车。要建造另一个车库来满足不断增长的需求,您可以克隆车库和客户访问列表。但是(物理的)汽车不应该被克隆。那是因为车库不是由汽车组成的。
正如我们所看到的,聚合和组合在概念上的区别对关系的实现有着重要的影响。如果为True,则这两个关系在Java中都表示为引用。但是,组合通常需要深度克隆(如果支持克隆),其中每个父级负责克隆其自己的状态并递归地克隆其子级。
如上所述,如果您的对象是不可变的,则根本不需要支持克隆,因为您无论如何都无法区分原始对象和克隆对象。