8.4.3 覆盖、重新定义和重新引入方法
正如我们刚才所看到的,要在子类中覆盖一个延迟绑定的方法,需要使用override关键字。只有当该方法在祖先类中被定义为虚方法时,才能进行重载。如果该方法被定义为动态(dynamic)方法(我们稍后将介绍另一个关键字),也可以覆盖该方法。否则,如果该方法未被标记为virtual或dynamic,,则会被视为静态方法,除了修改祖先类的代码外,不能使用延迟绑定对其进行修改。
注解:你可能还记得,在上一章中,我也使用了相同的关键字来覆盖从基类 TObject 继承而来的 Destroy 默认析构函数。
规则很简单: 定义为静态方法在每个子类中仍然保持静态,除非你用一个新的同名虚方法将其隐藏。定义为 virtual 的方法在每个子类中都是延迟绑定的。由于编译器会为延迟绑定的方法生成不同的代码,因此无法改变这种情况。
要重新定义静态方法,只需在子类中添加一个与原方法参数相同或不同的方法,而无需任何进一步的说明。要覆盖虚方法,必须使用相同的参数并使用override关键字:
type
TMyClass = class
procedure One; virtual;
procedure Two; // 静态方法
end;
TMySubClass = class(TMyClass)
procedure One; override;
procedure Two;
end;
重新定义的方法Two没有延迟绑定。因此,当你将它应用于基类的变量时,无论如何它都会调用基类方法,即使变量指向的是派生类的对象,而派生类的方法也有不同的版本。
覆盖方法通常有两种方式。一种是用全新的版本替换父类的方法。另一种是在现有方法中添加更多代码。第二种方法可以通过使用inherited关键字来调用祖先类的同名方法来实现。例如,你可以编写:
procedure TMySubClass.One;
begin
// new code
...
// Call inherited procedure TMyClass.One
inherited One;
end;
你可能想知道为什么需要使用override关键字。在其他语言中,当你在子类中重新定义一个虚方法时,你会自动覆盖原来的方法。但是,有override这个具体的关键字,编译器就可以检查祖先类中的方法名称与子类中的方法名称之间的对应关系。编译器还会检查该方法在父类中是否为虚方法,等等。
注解:另一种流行的 OOP 语言 C# 也有同样的override关键字。这并不奇怪,因为这两种语言有一个共同的设计者。Anders Hejlsberg 有一些长篇文章解释了为什么override关键字是设计程序库的基本版本工具。更多信息请访问 http://www.artima.com/intv/nonvirtual.html。最近,苹果的 Swift 语言也采用了override关键字来修改派生类中的方法。
这个关键字的另一个优点是,如果你在任何类中定义了一个静态方法,然后被程序库中的类继承,那么即使程序库更新了一个与你定义的方法同名的新的虚方法,也不会有任何问题。因为你的方法没有被override关键字标记,这个方法将被视为一个独立的方法,而不是添加到库中的方法的新版本(这很可能会破坏你现有的代码)。
对重载的支持进一步增加了这种情况的复杂性。子类可以使用Overload关键字为方法提供一个新版本。如果该方法的参数与基类中的版本不同,它实际上就成为了一个重载方法;否则,它就取代了基类方法。下面是一个例子:
type
TMyClass = class
procedure One;
end;
TMySubClass = class(TMyClass)
procedure One(S: string); overload;
end;
请注意,该方法在基类中无需标记为重载。但是,如果基类中的方法是虚方法,编译器就会发出警告:“方法’One’隐藏了基类类型’TMyClass’的虚方法”。
为了避免编译器发出这条信息,并更准确地指示编译器您的意图,您可以使用特定的 reintroduce 指令:
type
TMyClass = class
procedure One; virtual;
end;
TMySubClass = class(TMyClass)
procedure One(S: string); reintroduce; overload;
end;
您可以在 ReintroduceTest 示例中找到这段代码,并进一步尝试使用。
注解:使用 reintroduce 关键字的一种情况是,你想为一个组件类添加一个自定义的 Create 构造函数,而该组件类已经从 TComponent 基类继承了一个Create 虚构造函数。