8.5 抽象方法与类:
在创建类的层次结构时,有时很难确定哪个是基类,因为基类可能并不代表实际的实体,而只是用来保存某些共享行为。例如,猫或狗类的动物基类。这种不需要创建任何对象的类通常被称为抽象类,因为它没有具体和完整的实现。抽象类可以有抽象方法,即没有实际实现的方法。
8.5.1 抽象方法
abstract
关键字被用于声明虚方法,这种虚方法只能在当前类的子类中定义。abstract指令完全定义了方法,不是前向声明。如果尝试定义该方法,编译器将发出警告。
在Object Pascal
中,您可以创建具有抽象方法的类的实例。但是,当您尝试这样做时,编译器会发出警告消息:Constructing instance of containing abstract methods。如果您碰巧在运行时调用了抽象方法,Delphi 将引发特定的运行时异常。
注解: C++、Java和其他语言使用了更严格的方法:在这些语言中,您不能创建抽象类的实例。
你可能会问,为什么要使用抽象方法?原因在于多态性的运用。如果类 TAnimal
有虚抽象方法 Voice
,每个子类都可以重新定义它。
这样的好处是,现在你可以使用通用的FMyAnimal
对象来引用由子类定义的每个动物并调用该方法。如果该方法不存在于 TAnimal
类的接口中,编译器就不会允许调用,因为编译器会执行静态类型检查。使用通用的FMyAnimal
对象,你只能调用由其自身类TAnimal
定义的方法。
除非父类至少具有此方法的声明(以抽象方法的形式),否则无法调用由子类提供的方法。下一个示例 "Animals3 "演示了抽象方法的使用和抽象调用错误。下面是这个新示例的类的接口:
type
TAnimal = class
private
FKind: string;
public
constructor Create;
function GetKind: string;
function Voice: string; virtual; abstract;
end;
TDog = class(TAnimal)
public
constructor Create;
function Voice: string; override;
function Eat: string; virtual;
end;
TCat = class(TAnimal)
public
constructor Create;
function Voice: string; override;
function Eat: string; virtual;
end;
最有趣的部分是TAnimal
类的定义,其中包含一个虚抽象方法Voice
。同样重要的是,每个派生类都会覆盖这个定义,并添加一个新的虚拟方法:Eat。这两种不同的方法会产生什么影响呢?要调用 Voice 方法,我们只需编写与前一版本程序相同的代码即可:
Show(MyAnimal.Voice);
那么如何调用Eat
方法?我们无法将其应用于TAnimal
类的对象。语句
Show(MyAnimal.Eat);
生成编译器错误“字段标识符应为预期标识符( Field identifier expected)”。
要解决这个问题,可以使用一个动态的和安全的类型转换,将 TAnimal
对象视为 TCat 或 TDog 对象,但这种方法非常麻烦,而且容易出错:
begin
if MyAnimal is TDog then
Show(TDog(MyAnimal).Eat)
else if MyAnimal is TCat then
Show(TCat(MyAnimal).Eat);
这段代码将在后面的 "安全类型转换运算符 "一节中解释。在 TAnimal 类中添加虚方法定义是解决问题的典型方案,因为所有动物都吃东西,而且abstract关键字的存在也有利于这种选择。上面的代码看起来很丑陋,而避免这样的代码正是使用多态性的原因,而且在这种情况下,它也代表了我们的实现所模拟的真实世界。
最后,请注意,当一个类有一个抽象方法时,它通常被认为是一个抽象类。不过,你也可以用 abstract 指令特别标记一个类(即使它没有抽象方法,也会被认为是一个抽象类)。同样,在 Object Pascal 中,这不会阻止你创建该类的实例,所以在这种语言中,抽象类声明的用处相当有限。