To LiChaoHui:
接口的使用更有弹性,但接口也有继承呀!第一,接口可以继承接口,第二,接口由类来实现,实现类必然要继承该接口。所以用接口实现多态离不开继承。没有继承就没有多态。下面摘录《Delphi面相对象编程思想》中“接口和面相对象编程”一节。
=================================
接口语言可以看作包含抽象函数的最终抽象类。甚至从句法上讲,接口与只包含抽象函数的抽象类相似。
一个类可以实现一个接口,如此一来,类就要实现接口的抽象函数成员,与派生类保证要实现它的基类的抽象函数一样。所以接口是通过动态绑定函数调用抽象基类的替代方式。例如前面示例程序 6 1和示例程序 6 2中,我们可以用一个等价的IGreetable接口来替换TMan类,此接口也包含一个抽象的SayHello方法。TChinese、TAmerican、TFrench等派生类也将实现IGreetable接口。这不会影响动态绑定调用SayHello函数的能力。不过这个例子还不能演示和说明接口的威力和妙用。
即使接口和抽象类从句法和语义上密切相关,他们仍有一个重要的区别:接口只能包含抽象方法,而抽象类除了包含抽象方法外,还可以包含其他数据成员和非抽象方法。另外,一个类最多只有一个基类,但可以有多个接口。
一个接口可以自身实现一个或多个接口,从而继承这些接口的方法,形成与类层次一样的接口层次。
在前面的内容中,普通类形成了健全的分类类层次,其中每一对基类和派生类表示一个“is-a”关系。这些层次提供了极大的好处,但也从一定程度上限制了多态的使用。原因是:在一个类层次中要充分利用多态,必须具备一组相同的祖先类。此祖先类声明了接口(通常以抽象方法形式)以及派生类中单个实现过程间的通信协议。如果我们想要划分成组的类不具有相同的祖先类怎么办呢?如果他们分布于整个程序的多个地方,甚至位于多个单独的类层次中,又该如何处理?
图 6 11接口让3个无关的类实现相同的抽象方法
假如我们重新考虑“来自世界的问候”的那个例子,将类层次由一个TMan增加到TUnkown、TMachine等数个,如图 6 11所示。其中,我们希望机器人和外星人都能像人类一样进行问候,都有自己的SayHello方法,尽管可能问候的方式不同。但我们无法将它们归纳到一个共同的祖先类。如果硬要用一个抽象类放置到TMan、TUnkown和TMachine的上层,并包含一个抽象方法SayHello的话,虽然可以工作,但却扰乱了类的层次关系,最终导致设计概念上的不伦不类关系。
最理想的解决方式是创建一个接口,取名为IGreetable,用它来包含抽象方法SayHello,并让3个类实现这一接口,如图 6 11所示。该接口声明如下:
IGreetable= interface
['{D91DDE09-0FC4-4FE9-AE0D-9877E2F73BF6}']
function SayHello:string;
end;
在前面“来自世界的问候”的那个例子中,我们已经初步体验到多态的作用。
在示例程序 6 7中,TMan、TET和TRobot三个没有血缘关系的类都继承自IGreetable,他们实现SayHello方法的内容也不一样。有意思的是在TfrmSayHello中他们仍然可以用一个sayhello(greeting:IGreetable)来实现多态,如示例程序 6 7和图 6 12所示。
示例程序 6 7 扩充新类TET和TRobot后的uSayHello
unit uSayHello;
interface
type
IGreetable= interface
['{D91DDE09-0FC4-4FE9-AE0D-9877E2F73BF6}']
function SayHello
Char;
end;
TMan = class (TInterfacedObject,IGreetable)
public
Language: string;
Married: Boolean;
Name: string;
SkinColor: string;
constructor create; virtual;
function SayHello
Char;virtual;abstract;
end;
TChinese = class (TMan)
public
constructor create;override;
private
function SayHello
Char;override;
end;
TAmerican = class (TMan)
public
constructor create;override;
private
function SayHello
Char;override;
end;
TFrench = class (TMan)
public
constructor create;override;
private
function SayHello
Char;override;
end;
TKorean = class (TMan)
public
constructor create;override;
private
function SayHello
Char;override;
end;
TET = class (TInterfacedObject,IGreetable)
private
function SayHello
Char;
end;
TRobot = class (TInterfacedObject,IGreetable)
private
function SayHello
Char;
end;
implementation
constructor TMan.create;
begin
Name:='张三';
Language:='中文';
SkinColor:='黄色';
end;
constructor TChinese.create;
begin
inherited;
end;
constructor TAmerican.create;
begin
Name:='Lee';
Language:='英文';
SkinColor:='黑色';
end;
constructor TFrench.create;
begin
Name:='苏菲';
Language:='法文';
SkinColor:='白色';
end;
constructor TKorean.create;
begin
Name:='金知中';
Language:='韩文';
SkinColor:='黄色';
end;
function TChinese.SayHello;
begin
Result:='chinese.bmp';
end;
function TAmerican.SayHello;
begin
Result:='American.bmp';
end;
function TFrench.SayHello;
begin
Result:='French.bmp';
end;
function TKorean.SayHello;
begin
Result:='Korean.bmp';
end;
function TET.SayHello;
begin
Result:='ET.bmp';;
end;
function TRobot.SayHello;
begin
Result:='Robot.bmp';;
end;
end.
示例程序 6 8扩充新类TET和TRobot后的ufrmSayHello
unit ufrmSayHello;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls,uSayHello;
type
TfrmSayHello = class(TForm)
btnUSA: TButton;
btnKorean: TButton;
btnCN: TButton;
btnFrench: TButton;
Image1: TImage;
btnET: TButton;
btnRobot: TButton;
procedure btnUSAClick(Sender: TObject);
procedure btnCNClick(Sender: TObject);
procedure btnFrenchClick(Sender: TObject);
procedure btnKoreanClick(Sender: TObject);
procedure btnETClick(Sender: TObject);
procedure btnRobotClick(Sender: TObject);
private
procedure sayhello(greeting:IGreetable);
public
{ Public declarations }
end;
var
frmSayHello: TfrmSayHello;
implementation
{$R *.dfm}
procedure TfrmSayHello.sayhello(greeting:IGreetable);
begin
image1.Picture.LoadFromFile(greeting.sayhello);
end;
procedure TfrmSayHello.btnUSAClick(Sender: TObject);
begin
sayhello(TAmerican.create);
end;
procedure TfrmSayHello.btnCNClick(Sender: TObject);
begin
sayhello(TChinese.create);
end;
procedure TfrmSayHello.btnFrenchClick(Sender: TObject);
begin
sayhello(TFrench.create);
end;
procedure TfrmSayHello.btnKoreanClick(Sender: TObject);
begin
sayhello(TKorean.create);
end;
procedure TfrmSayHello.btnETClick(Sender: TObject);
begin
sayhello(TET.create);
end;
procedure TfrmSayHello.btnRobotClick(Sender: TObject);
begin
sayhello(TRobot.create);
end;
end.
图 6 12 使用IGreetable接口后,外星人也能SayHello
从示例程序 6 7中,我们可以看出IGreetable接口的SayHello方法在 TMan、TET和TRobot中实现方法也有区别。TET和TRobot中是直接实现SayHello方法的。而TMan并没有这样做,他只是将SayHello方法作为抽象方法留给派生类去实现。
请注意,继承关系常常描述成“is-a” (是一个)关系,例如,TChinese“is-a”TMan。在Delphi的is运算符中可以看到相同的概念,它可以用于测试一个AMan变量是否“is-a”TChinese。但是,在很多复杂的情况下,简单的“is-a”关系会被打破。比如,中国人“是一个”人,但机器人并不“是一个”人,这并不意味着机器人不能用自己的方式向你问候。可我们决不会让机器人从TMan继承下来。类继承迫使派生类存储所有在基类中声明了的数据成员,在这种情况下,会导致派生类并不需要那样的信息,比如:机器人不会需要肤色(没有皮肤的金属机器人)。尽管如此,类继承是代码重用的一个有效工具。派生类能轻易地继承基类的数据成员、方法和属性,从而避免重复实现普通的方法。比如,TChinese 需要继承TMan 的Language、Married、Name、SkinColor数据成员和create、SayHello方法。
通过面向对象的分析,我们可以将中国人和机器人泛化成能问候(something greetable)的某种类型对象,并抽象出IGreetable接口。这样使用接口还有一个最大的好处就是将类型继承与类继承分离开来。在一个强类型化的语言,如Delphi中,编译器将类作为类型对待,因此类继承变成与类型继承了。但是我们要明白:类是一种类型,类的继承和类型的继承并不完全一样。比如,接口的继承就是一种类型的继承,他和类的继承决不等同。前面我们就是将类型继承(中国人和机器人都是一种能问候的类型)与类继承(类TChinese继承类TMan的数据成员和方法)分离开来。这就是说,在类型继承时使用接口,以提高程序可扩展性;而类继承仅在需要用到的地方使用,继承了的数据成员和方法可提高程序的可重用性。下面举例说明。
为了进一步演示类继承和类型继承的差别和类型继承与类继承分离的好处,我们修改一下前面“来自世界的问候”的那个例子如下:
示例程序 6 9 进一步修改的uSayHello
unit uSayHello;
interface
type
IGreetable= interface
['{D91DDE09-0FC4-4FE9-AE0D-9877E2F73BF6}']
function SayHello
Char;
end;
TMan = class (TInterfacedObject,IGreetable)
public
Language: string;
Married: Boolean;
Name: string;
SkinColor: string;
constructor create; virtual;
function SayHello
Char;virtual;abstract;
end;
TChinese = class (TMan)
public
constructor create;override;
private
function SayHello
Char;override;
end;
TAmerican = class (TMan)
public
constructor create;override;
private
function SayHello
Char;override;
end;
TFrench = class (TMan)
public
constructor create;override;
private
function SayHello
Char;override;
end;
TKorean = class (TMan)
public
constructor create;override;
private
function SayHello
Char;override;
end;
TET = class (TInterfacedObject,IGreetable)
private
function SayHello
Char;
end;
TRobot = class (TInterfacedObject,IGreetable)
private
function SayHello
Char;
end;
implementation
constructor TMan.create;
begin
Name:='张三';
Language:='中文';
SkinColor:='黄色';
end;
constructor TChinese.create;
begin
inherited;
end;
constructor TAmerican.create;
begin
Name:='Lee';
Language:='英文';
SkinColor:='黑色';
end;
constructor TFrench.create;
begin
Name:='苏菲';
Language:='法文';
SkinColor:='白色';
end;
constructor TKorean.create;
begin
Name:='金知中';
Language:='韩文';
SkinColor:='黄色';
end;
function TChinese.SayHello;
begin
Result:='chinese.bmp';
end;
function TAmerican.SayHello;
begin
Result:='American.bmp';
end;
function TFrench.SayHello;
begin
Result:='French.bmp';
end;
function TKorean.SayHello;
begin
Result:='Korean.bmp';
end;
function TET.SayHello;
begin
Result:='外星人#$%^&*(@@)';
end;
function TRobot.SayHello;
begin
Result:='机器人0111000011110000111111';
end;
end.
示例程序 6 10进一步修改的ufrmSayHello
unit ufrmSayHello;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls,uSayHello;
type
TfrmSayHello = class(TForm)
GroupBox1: TGroupBox;
edtName: TLabeledEdit;
edtSkinColor: TLabeledEdit;
edtLanguage: TLabeledEdit;
btnUSA: TButton;
btnKorean: TButton;
btnCN: TButton;
btnFrench: TButton;
Image1: TImage;
btnET: TButton;
btnRobot: TButton;
procedure btnUSAClick(Sender: TObject);
procedure btnCNClick(Sender: TObject);
procedure btnFrenchClick(Sender: TObject);
procedure btnKoreanClick(Sender: TObject);
procedure btnETClick(Sender: TObject);
procedure btnRobotClick(Sender: TObject);
private
procedure sayhello(AMan:TMan);overload;
procedure sayhello(greeting:IGreetable);overload;
public
{ Public declarations }
end;
var
frmSayHello: TfrmSayHello;
implementation
{$R *.dfm}
//这里的实现方法和原来(示例程序 5 6)的一样,没有改动
procedure TfrmSayHello.sayhello(AMan:TMan);
begin
edtName.Text:=AMan.Name;
edtLanguage.Text:=AMan.Language;
edtSkinColor.Text:=AMan.SkinColor;
image1.Picture.LoadFromFile(AMan.sayHello);
end;
//这里新增来自非人类的另类问候实现方法。
//和示例程序 5 6比较,这是唯一的改动。
//通过方法重载,使程序的改动降低到最少。
procedure TfrmSayHello.sayhello(greeting:IGreetable);
begin
edtName.Text:=copy(greeting.sayhello,1,6);
edtLanguage.Text:=copy(greeting.sayhello,7,4);
edtSkinColor.Text:=copy(greeting.sayhello,11,6);
application.MessageBox(greeting.sayhello,
'问候',MB_ICONINFORMATION+MB_OK);
end;
procedure TfrmSayHello.btnUSAClick(Sender: TObject);
begin
sayhello(TAmerican.create);
end;
procedure TfrmSayHello.btnCNClick(Sender: TObject);
begin
sayhello(TChinese.create);
end;
procedure TfrmSayHello.btnFrenchClick(Sender: TObject);
begin
sayhello(TFrench.create);
end;
procedure TfrmSayHello.btnKoreanClick(Sender: TObject);
begin
sayhello(TKorean.create);
end;
procedure TfrmSayHello.btnETClick(Sender: TObject);
begin
sayhello(TET.create);
end;
procedure TfrmSayHello.btnRobotClick(Sender: TObject);
begin
sayhello(TRobot.create);
end;
end.
大家注意到我在示例程序 6 10中分别重载了sayhello方法,于是可以在TfrmSayHello(实际上是界面类,通常是位于表示层中)中将人类的和非人类的sayhello方法分别实现,不过实现的代码完全不一样。
procedure sayhello(AMan:TMan);overload;
procedure sayhello(greeting:IGreetable);overload;
虽然按钮单击事件中的写法都是Txxx.create,但是作为TMan的派生类都能通过重载找到适合自己的sayhello(AMan:TMan)方法,并实现自己的显示内容;TET和TRobot也能通过重载找到适合自己的sayhello(greeting:IGreetable)方法,实现与TMan的派生类完全不同的显示内容。值得注意的是,示例程序 6 10中的TfrmSayHello.sayhello方法已经不仅仅像示例程序 6 8中那样仅有一条代码:
image1.Picture.LoadFromFile(greeting.sayhello);
这意味着类型继承与类继承分离后,我们可以享受到鱼和熊掌兼得的好处:既通过接口实现多态的灵活性,又通过继承获得代码的重用性。实际上运行该程序,我们也能够立即观察到它既保留了原有的功能(来自世界各国的问候),又扩展了新的功能(来自非人类的另类问候)。
这个例子巧妙地用到了覆盖、重载、继承、多态、抽象方法和抽象类、虚方法、接口、类的类型转换(类的向上转型,类向继承的接口转型)等众多概念,如果能够深入理解掌握,必将会使你在面向对象编程方面功力大增,值得读者好好研习和体会。
---------------
我的个人网站:http://www.liu-yi.net