第8章 继承
如果说编写类的关键原因是封装,那么在类之间使用继承的关键原因就是灵活性。将这两个概念结合起来,你就能拥有可以使用且不会改变的数据类型,并能创建这些类型的修改版本,这就是最初所谓的 “开放-封闭原则”:
“软件实体(类、模块、函数等)应该开放以进行扩展,但封闭以进行修改。”
——Bertrand Meyer,《面向对象软件构造》,1988年
诚然,继承是一种非常强的约束,会导致代码的紧密耦合,这是现在不推荐的做法,但继承也为开发人员提供了巨大的能力(是的,同时也带来了更多的责任)。
在此,我不想就这一特性展开讨论,而是想向大家介绍类型继承的工作原理,特别是在 Object Pascal 语言中的工作原理。
8.1 从现有类型继承
我们经常需要使用我们自己编写或别人给我们的现有类的略有不同的版本。
例如,您可能需要添加一个新方法或对现有方法稍作修改。除非你想在不同情况下使用该类的两个不同版本,否则你可以通过修改原始代码来轻松实现。此外,如果该类最初是由其他人编写的(而你是从库中找到的),你可能希望将自己的修改分开。
如果一个类有两个相似的版本,一种典型的老式方法是复制原始类型定义,修改代码以支持新特性,然后给生成的类取一个新名字。这种方法也许可行,但也可能带来问题:在复制代码的同时,也复制了错误;当其中一份代码中的错误被修复后,你必须记得将修复应用到另一份代码中;如果你想添加一个新特性,你需要添加两次或更多次,这取决于你随着时间推移所复制的原始代码的数量。即使在第一次编写代码时这样做不会拖慢您的速度,但这种方法对于软件维护来说却是一场灾难。此外,这种方法会导致两种完全不同的数据类型,因此编译器无法帮助您利用两种类型之间的相似之处。
为了解决表达类之间相似性时出现的这类问题,Object Pascal
允许你直接从已有的类定义一个新类。这种技术被称为继承(或子类化,或类型派生),是面向对象编程语言的基本要素之一。
要从现有类继承,只需在子类声明的开头注明该类即可。实际上,每次创建新窗体时,系统都会自动执行这一操作:
typeTForm1 = class(TForm)...end;
这个简单的定义表示 TForm1 类继承了 TForm 类的所有方法、字段、属性和事件。您可以将 TForm 类的任何公共方法应用于 TForm1 类型的对象。反过来,TForm 又从另一个类继承了一些方法,依此类推,直到 TObject 类(它是所有类的基类)。相比之下,C++、C#和Java可能会使用类似以下的语法:
class Form1: TForm
{
...
}
作为演示继承的一个简单示例,我们可以对上一章的 ViewDate 示例稍作修改,从 TDate 派生一个新类,并修改其中的一个函数 GetText。你可以在 DerivedDates 示例的 Dates.pas 文件中找到这段代码。
typeTNewDate = class(TDate)publicfunction GetText: string;end;
在本例中,TNewDate
是派生自 TDate 的。通常可以说 TDate 是 TNewDate 的祖先类、基类或父类,而 TNewDate 是 TDate 的子类、后代类或子类。
为了实现新版本的 GetText 函数,我使用了 FormatDateTime
函数,该函数使用(除其他功能外)预定义的月份名称。下面是 GetText 方法,其中 "dddddd"代表长日期格式:
function TNewDate.GetText: string;
beginResult := FormatDateTime('dddddd', FDate);
end;
定义新类后,我们需要在 DerivedDates
项目的窗体代码中使用这种新数据类型。只需定义 TNewDate
类型的 ADay 对象,并在 FormCreate
方法中调用其自定义类型的构造函数即可:
typeTDateForm = class(TForm)...privateFDay: TNewDate; // 更新声明end;procedure TDateForm.FormCreate(Sender: TObject);
beginFDay := TNewDate.Create; // 更新行DateLabel.Text := FDay.GetText;
end;
无需任何其他更改,新的应用程序就能正常运行。
TNewDate 类继承了增加日期、添加天数等方法。此外,调用这些方法的旧版代码仍可正常工作。实际上,要调用新版本的 GetText 方法,我们不需要修改源代码!Object Pascal 编译器会自动将调用绑定到一个新方法上。
所有其他事件处理器的源代码完全保持不变,尽管其含义发生了很大变化,正如新的输出所演示的那样(见图 8.1)。
图8.1: DerivedDates程序的输出,其中月份和日期的名称取决于Windows区域设置