end;
TJet = class(TPlane)
public
constructor Create();
destructor Destroy(); override;
procedure fly(); override;
procedure land(); override;
…… //其他可能的操作,没有覆盖modal()
end;
TPlane类的声明中,fly和land方法都是被声明为virtual和abstract的,这是向编译器
指出这些方法是抽象(纯虚)的,也就是在TPlane类中不提供这些方法的实现,而派生类
则必须实现它,即规定了一套接口。凡是含有abstract方法的类被称为“抽象类”,永远无
法创建抽象类的实例对象。抽象类是被用来作为接口的。
现在,假设要完成一个机场管理系统,在有了以上的TPlane之后,再编写一个全局的
函数g_FlyPlane(),就可以让所有传递给它的飞机起飞:
procedure g_FlyPlane(const Plane : TPlane);
begin
Plane.fly();
end;
是的,仅仅如此就可以让所有传给它的飞机(TPlane的派生类对象)正常起飞!不管
是直升机还是喷气式飞机,甚至是现在还不存在的、以后会增加的飞碟。这是因为,每个
派生类(真正的飞机)都可以通过“override”来定义适合自己的起飞方式。
可以看到,g_FlyPlane()函数接受的参数是TPlane类对象的引用,而实际传递给它的都
是 TPlane的派生类对象。现在回想一下本节开头所描述的“多态”:多态性是允许将父对
象设置成为与一个或更多的它的子对象相等的技术,赋值之后,父对象就可以根据当前赋
值给它的子对象的特性以不同的方式运作。很显然,
parent := child;
就是多态的实质!这里的飞机类(TPlane)作为一种接口,而该接口就是被重用的目标。
多态的本质就是“将派生类类型的指针赋值给基类类型的指针”(在Object Pascal中
是引用),只要这样的赋值发生了,就是在应用多态了,因为在此实行了“向上映射”(“上
下”是指类继承层次关系)。
应用多态的例子非常普遍。在Delphi的VCL类库中,最典型的就是:TObject类有一
个虚拟的Destroy析构函数和一个非虚拟的Free方法。Free方法中首先判断对象本身是否
为nil,保证不为nil时便调用Destroy。对任何对象(都是TObject的派生类对象)调用其
Free();方法,但执行的都是TObject.Free();(因为TObject.Free()为非虚拟方法,无法被覆盖),
然后由它调用被每个类重定义了的析构函数Destroy();(因为Destroy()为虚方法,派生类可
以覆盖),这就保证了任何类型的对象都可以正确、安全地被析构。
因此,在定义自己的类时,如果有析构函数存在,就必须在它的声明之后加上override
关键字。否则会发生什么呢?
还拿刚才的飞机类作为例子,有一个飞机销毁站,这个销毁站有一个函数:
procedure DestroyPlane(var Plane : TPlane)
begin
Plane.Free();
Plane := nil
end;
将正常的飞机(析构函数带有override的飞机)传给它,编译器都会正常地调用飞机
的析构函数用以将飞机拆解开,将资源正常回收。
如果建造的飞机的析构函数没有被指明override关键字,那么,将飞机传递给这个
DestroyPlane()的函数时,编译器调用的绝对不是用户所给飞机定义的析构函数,而会是
TPlane.Destroy()。能指望TPlane的析构函数会好好拆解飞机吗?祈祷吧,别把油箱弄爆
炸了。
也就是说,在被执行了
parent := child;
之后,无论调用
parent.Free();
还是调用
child.Free();
都应该产生同样的结果,从语义上来说,这两行代码必须做相同的事情。当然,这样的情
况不仅仅只对于析构函数而言,任何想要使通过基类对象指针做到的事情与通过派生类对
象指针所做的相同,就要在基类中将这个方法声明为virtual,在派生类中将该方法声明为
override。
..注意:给自己的析构函数加上override声明!
多态的实现与VMT/DMT
多态的本质是