OpenDSA 完整目录

Chapter 0 Introduction to Software Design

| 关于   «  1. 对象、类与微观世界   ::   目录   ::   3. 方法、选择与重复动作  »

2. 面向对象继承、重写与接口

在前面的章节中,你学习了如何创建子类和定义自定义方法。在本章中,你将探索面向对象继承的全部力量:在以 Java 通用基类 Object 为根的类层次结构中导航、通过方法重写( @Override )特化行为,以及利用运行时多态和动态方法分派来编写灵活、可扩展的软件系统。

2.1. 类层次结构与继承

类之间是如何关联的?在 Java 和任何其他面向对象的语言中,类以 类层次结构 组织。类层次结构类似于一棵倒置的树。层次结构的最顶部是最通用的类。在 Java 中,最通用的类是 Object 类。层次结构中 Object 下方的类被称为其 子类 。由于我们在程序中使用的所有对象都属于某个类,这就相当于说所有对象都是 Object 。

下图使用本节中描述的类说明了类层次结构的概念。注意 Object 类位于层次结构的顶部。它是最通用的类,具有所有 Java 对象共有的特性。当你沿层次结构向下移动时,类变得越来越特化。 Rectangle 是一个 Object ,但它包含的属性——length 和 width——是所有矩形共有的,而非层次结构中其他对象的共有属性。例如, ATM 对象不一定有长度和宽度。注意我们向层次结构中添加了一个 square 类。 square 是 Rectangle 的一种特殊类型,即长度等于宽度的矩形。

_images/ClassHierarchy.png

为了引入与此类层次结构相关的一些重要术语,我们说 Rectangle 类是 Object 类的子类。 square 类同时是 square 和 Object 的子类。在层次结构中位于给定类上方的类被称为其 超类 。因此 Rectangle 类是 square 类的超类。 Object 类也是 square 的超类。通常,我们说子类 扩展 了超类,意味着它在超类所包含的内容基础上添加了额外的元素(属性和/或方法)。我们在 square 类的案例中看到了这一点。它添加了长度和宽度始终相等的特性。

与类层次结构相关的另一个重要概念是 类继承 ,即子类从其超类继承所有属性和方法。以自然界为例,想想马和哺乳动物之间的继承关系。马是哺乳动物,因此马凭借也是哺乳动物这一事实继承了温血的特征。(这不同于个体层面的继承,即你继承母亲的蓝眼睛和父亲的黑头发。)

为了说明继承在编程中的工作方式,请考虑一个国际象棋程序。有几种不同类型的 ChessPiece 对象: Pawn (兵)、 Knight (马)、 Queen (后)和 King (王)。下图说明了棋子层次结构。

_images/ChessPieceHierarchy.png

所有棋子共有的一对属性是它们在棋盘上的 row 和 column 位置。因为所有棋子都共有这些属性,它们位于 ChessPiece 层次结构的顶部,并被所有 ChessPiece 子类继承。当然,每个 ChessPiece 对象中 row 和 column 属性的值各不相同。

所有棋子共有的一个动作是它们可以 moveTo() 棋盘上的给定位置。但不同类型的棋子有不同的移动方式。例如, Bishop (象)只能沿棋盘的对角线移动,而 Rook (车)只能沿棋盘的 row 或 column 移动。因此,显然我们无法描述一个适用于所有 ChessPiece 的 moveTo() 方法。这就是为什么我们将 moveTo() 方法放在所有 ChessPiece 子类中。 ChessPiece 类也有一个 moveTo() 方法,但请注意其在图中以斜体显示,表示它无法在该级别完整定义。

最后,注意在国际象棋中,王具有一些特殊的属性和动作。只有王可以被 将军 (in check),这意味着王正在被攻击并面临被吃掉的危险,从而结束比赛。同样,只有王可以王车易位(castle),这是王与一个车在特定条件下共同完成的特殊移动。因此,我们在 King 类中显示 inCheck 属性和 castle() 动作,是因为这些是 King 对象特有的特征。

这样,类层次结构表示了从上到下移动时类的 特化 。最通用的类 ChessPiece 位于层次结构的顶部。它的属性和方法被传递(继承)给其子类。然而,除了从超类继承的属性和方法外,子类还定义了自己特有的属性和方法。每个子类——Pawn 、 Bishop 等——代表了超类的某种特化。在本例中,每个子类都有自己独特的移动方式,而 King 子类拥有独特的属性和动作( inCheck 和 castle() )。

2.2. 什么是多态?

所有 Jeroo 都理解相同的消息集合——也就是说,它们响应相同的方法调用。但既然你已经学习了如何定义自定义方法,子类也可以 重新定义 方法来做更多事情或改变其行为。事实证明,不同的 Jeroo 可能以不同的方式响应 相同的方法调用 ,具体取决于它们所属的子类。我们使用术语 接收者 (receiver)来指代调用方法的对象。每次调用方法时,接收者决定如何响应,因此具体行为取决于方法在用于创建接收者的特定子类中的定义方式。

多态 意味着不同的接收者可以以不同的方式响应相同的方法调用。多态不仅仅是一个理论概念;它是编写干净、灵活和可维护代码的强大工具。本质上,它允许单个接口表示多种底层形式。例如,如果你有一个 Vehicle 超类和像 Car 、 Bicycle 和 Truck 这样的子类,一个接受 Vehicle 作为参数的函数可以与这些子类中的任何一个一起工作,而无需在编译时知道它们的具体类型。这对于构建可扩展系统非常有用。你可以添加一个新的子类,如 Motorcycle ,你现有的处理 Vehicle 对象的代码仍然可以正常工作,无需任何更改。这种 单一接口、多种实现 的概念就是多态在实践中的核心优势。

2.3. 编程练习 7

   «  1. 对象、类与微观世界   ::   目录   ::   3. 方法、选择与重复动作  »

关闭窗口