delphi-多态(overload,override)与接口(interface)

转(智在居士草堂)
先来聊聊重载问题(也叫超载,总之是那个Overload)吧。在同一个类中,出现多个同名的方法的现象就是Overload。联系起上面提到的兄弟俩,我想这更好理解。他们从外观上是相似的,不同的是某些“参数”,类型上可能各异或是个数上不同。
在Delphi中,所有方法都默认是静态的,便于编译器和链接器解析调用。为实现超载,要在方法明的后面加上关键字overload,不然会产生报错。先看个小例子吧:
type            //这是Delphi对类Ta的定义部分
 Ta=class 
 public
   procedure XiongDi(age,height:integer);overload; //这是Ta的两个公共过程,名字一样
   procedure XiongDi(name:string);overload;     //但他们的参数不同。
 end;
implementation //以下为实现部分
procedure Ta.XiongDi(age, height: integer);     //这是两个过程的实现。
begin
Form1.Edit1.Text:='age: '+inttostr(age)+' height: '+inttostr(height);
end;
procedure Ta.XiongDi(name: string);
begin
Form1.Edit2.Text:='name: '+name;
end;
{Delphi用很强的图形化控件,
我们用一个窗体的Button控件来激活上述过程,并用Edit控件显示结果。}
procedure TForm1.Button1Click(Sender: TObject); 
var
a:Ta; //这是Delphi对对象的定义,a是Ta的实例化,就是Ta这个类的对象。
begin
a.XiongDi(15,130);
a.XiongDi('David');
这是效果图。
我们看到第一个XiongDi反映的是年龄和身高信息,而第二个XiongDi则反映的是姓名信息。它们的参数个数和类型都不同,要表达的效果也就不同。所以,面对Overload我们不要被表面上的名称所迷惑,而应该了解函数、过程和方法实质的作用。多说一句,函数和过程是Delphi里的叫法,Java中统成为方法,无返回值的方法用void关键字表示,代表Delphi里的过程,而有返回值的方法就是Delphi中的函数。
再来说说在Java中实现Overload。Java的方法和Delphi刚好相反,所有方法都默认成虚拟的,所以Java中可以随时Overload而不需要标明我要超载这个方法了。为了对比,我在Java中写个和上面一样的例子:
A.java文件:
class A{ //这是Java中对类A的定义。
 public void XiongDi(int age,int high){ //这里定义了两个同名方法
    System.out.println("age: "+age+" high: "+high);
 }

 

 public void XiongDi(String name){
    System.out.println(name);
 }
}
Do.java文件:
public class Do{
 public static void main(String[] args){
   A a=new A();
   a.XiongDi(15,130);
   System.out.println(" **********");
   a.XiongDi("David");
 }
}
编译并执行的结果。
好了,到此我们对delva的overload算是有了一个感性的认识了。闲话少叙,就先在Delphi中来个覆盖的小例子吧:
type
 Tfather=class //我们定义一个父类
 public
   function throwhand:string; virtual;
   end;
type
 Tson=class(Tfather)  //我们定义一个继承父类的子类
 public
   function throwhand:string;override;
   end;

 

//这是两个函数的实现:
{ Tfather }
function Tfather.throwhand: string;
begin Form1.edit1.Text:='right'; end;
{ Tson }
function Tson.throwhand: string;
begin Form1.edit2.Text:='left';   end;

 

//这是对函数的调用:
procedure TForm1.Button1Click(Sender: TObject);
var FatherUse:Tfather; SonUse:Tson;
begin
FatherUse:=Tfather.Create;
SonUse:=Tson.Create;

 

FatherUse.throwhand;
SonUse.throwhand;

 

FatherUse.Free;
SonUse.Free;
end;
这是效果图:
我们看到,父类被一个叫virtual的关键字所修饰。这是何故?不加行吗?当然不行,原因就是前面所述,Delphi的方法都是静态的。我们知道,Java中,被修饰成abstract 或final的方法是不能被覆盖的。由此可知,Delphi的默认的静态方法也不能被覆盖。因此,Delphi定义了两个关键字:virtual(虚拟)和dynamic(动态)。它们让函数和过程成为动态的,用于后关联。Virtual和dynamic作用大致相同。只是virtual的方法较dynamic的方法在VMT中占用空间较大,而执行时间较短。这是因为,子类在override父类中的某一个virtual方法时,VMT会为子类提供未被voerride的其它父类的virtual方法。而dynamic则只提供被override的方法。这两个方式个有好处,在Delphi中常被一起使用。
嗯,再多想想。如果父类的一个virtual方法不但要被子类方法override,还要被同一个类中的同名方法overload,那要怎么做呢?看看下面的例子:
type
 Tfather=class
 public   //多了关键字 overload
   function throwhand:string; overload;virtual;
   function throwhand(m:integer):integer; overload;virtual;
   end;
type
 Tson=class(Tfather)
 public    //多了关键字reintroduce,我发现不用它也行。但建议使用。
   function throwhand:string;reintroduce;override;
   end;
我们再来看看实现部分:
{ Tfather }
function Tfather.throwhand: string;
begin
 result:='right';
end;
function Tfather.throwhand(m: integer): integer;
begin
result:=m;
end;
{ Tson }
function Tson.throwhand: string;
begin
 Form1.edit2.Text:='left';
end;
这是调用部分:
procedure TForm1.Button1Click(Sender: TObject);
var
 FatherUse:Tfather;
 SonUse:Tson;
begin
FatherUse:=Tfather.Create;
SonUse:=Tson.Create;

 

Form1.edit1.Text:='usehand:'+FatherUse.throwhand+'throwfar:'+inttostr(FatherUse.throwhand(500))+'m';
SonUse.throwhand;

 

FatherUse.Free;
SonUse.Free;
end;
好了,Delphi的覆盖就聊到这里,嘻嘻,该来杯咖啡享受一下了。下面是Java的覆盖,它可没有那么多关键字。
这是Do.java文件的代码:
class A{   //这是父类
 void throwhand(){
    System.out.println("right!");
 }
}
class SA extends A{   //这是子类
 void throwhand(){
    System.out.print("left!");
 }
}
public class Do{
 public static void main(String[] args){
    A a=new A();   //父类实例化;
    SA s=new SA(); //子类实例化;
    System.out.println("-------------------------");
    System.out.print("Father throwhand is: ");
    a.throwhand();   //调用
    System.out.println(" ");
    System.out.print("Son throwhand is: ");   
    s.throwhand();   //调用
 }
}
这是效果图:

 

接口,这个词最初接触时,我对这个词义很难理解。因为它听起来更像与硬件相关的。还有一个词COM,它代表的意思就更乱,不同领域里出现的COM一词大不相同。但在明白接口一词其理后,我还得说译得很恰当,形象地说明了它的本质和用途。只是伟大、丰富的汉语表意太深。
为什么要用接口呢?不用不行吗?我没资格给你答案,但我知道接口的出现起码解决了如下问题。我们都知道,另外,接口是一种机制,在Delphi这种先声明,后实现的语言里,更显个性。因为,接口只声明/定义方法,而不实现。在Java中abstract类似乎可以替代接口,但我的理解是后者在继承层次增加后会突显其整洁的优势。
在Delphi里,从Delphi3加进了接口,并逐渐用它丰富了VCL,到了Delphi5时,Borland的接口技术在RAD中已趋于完美。嗯,当然,Delphi5是RAD最好的版本。以后的版本转向了在Web功能的丰富,大名鼎鼎的Web Service技术Delphi就是用接口实现的。5.0以前,接口的老祖宗是IUnknown,看上去就知道这是针对MicroSoft的那个我刚提过的,讨厌的名词COM的。在Delphi6推出时,正值Linux狼烟四起, Borland为了跨平台战略而大肆宣扬Kylix,这个祖宗变换了个大号,叫IInterface。我就是从那时起,知道了Kylix,看到了接口的广泛应用。
我们都知道,Delphi中的对象不能自动去活,它没有Java和C Sharp的垃圾回收机制。据李维先生说,Delphi之父Anders Hejelsberg当初设计Delphi时,让对象变量写入堆中而不是堆栈里,就是想在日后让Delphi可以自动回收闲置的对象。而直至今日,Danny Thorpe也没有让Delphi实现这个功能(虽然没有自动回收的Delphi已经几近完美)。反倒Anders Hejelsberg在到微软后实现了自己的想法,因为c sharp里已经实现了这个功能。可见C Sharp是Anders未尽理想的续篇。
嘿嘿,既然是闲谈,难免跑题。说自动去活是想说在Delphi里,接口是可以自动去活的。接口的对象计算引用数,使内存的管理自动化。Delphi的程序员更得了解引用技术值(Reference Count)。
下面就看看在Delphi里的接口吧。
我们先定义好接口和类:
type
 IMyidol=interface
 ['{30DDAB80-81BF-11D8-A27B-930F92738A22}'] //shift+ctrl+G 生成GUID
 procedure name(s:string); //接口方法不能实例化
 procedure others;
 end;
type
 TAnders=class(TInterfacedObject,IMyidol)
//继承一个通用类TInterfacedObject,并实现接口Imyidol。
 procedure name(s:string); //shift+ctrl+C
 procedure others;
 end;
然后实现:
procedure TAnders.name(s: string); //shift+ctrl+C 自动产生
begin
Form1.edit1.Text:=s;
end;

 

procedure TAnders.others;
begin

 

end;

 

procedure TForm1.Button1Click(Sender: TObject);
var
Anders:TAnders;
begin
Anders:=TAnders.Create;
Anders.name('Anders Hejlsberg!');
Anders.Free;
end;

 

procedure TForm1.Button2Click(Sender: TObject);
var LiWei:IMyidol;
begin
 LiWei:=TAnders.Create;
 LiWei.name('LiWei!'); //不必Free;
end;
这是效果图。
 
紧接着我们再来看看在Java中如何实现接口,然后我们再来对比着聊一聊。
interface Myidol{
 void name(String s);
 void others();
}

 

class SuperMan{ //定义一个类
}

 

class Man extends SuperMan implements Myidol{
//extends继承父类;implements实现接口。这里为了演示,其实可以不继承。
 public void name(String s){
    System.out.println(s);
 }
 public void others();
}

 

public class Do{
 public static void main(String[] args){
    Man man=new Man();
    man.name("Anders Hejlsberg!");
 }
}
看看效果图吧。
我们看到。Delphi的类在声明时不能写具体怎么实现,而要到implementation后面去写实现语句。而Java就不用这么“麻烦”,想到哪里就写到哪。这是他们的各自特点。也正因此,一个严谨,一个灵活。还有一个问题是,类可以继承多个接口。那接口可不可以继承多个接口呢?Java里的答案是肯定的,而Delphi7告诉你还的等,呵呵。可见,Java对接口的实现是最完美的。另外,Java没有Delphi的GUID,因为它不需要支持COM,也不需要像Delphi那样从祖宗接口继承。
回过头,观察一下代码我们会发现,others方法多余的,但还是在Delphi和Java里都写了空代码。此举多余乎?不多也。其实,接口有些私心,它让继承它的类必须一并实现它的方法,不然这个类就只能是适配器类或抽象类。哎,记住这一点吧。所以我在Delphi里没敢创建TsuperMan这个类,而是用通用的类TinterfacedObject。不然,我就得去实现这些:QueryInterface 、_AddRef、 _Release因为这是接口的老祖宗定义的。什么,连接口祖宗八代定义的方法都要在这个刚刚诞生的新类中去实现?是的。幸亏有TinterfacedObject这样的类。我们来看看它。
 TInterfacedObject = class(TObject, IInterface)
 protected
    FRefCount: Integer;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
 public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    class function NewInstance: TObject; override;
    property RefCount: Integer read FRefCount;
 end;
还有就是接口定义的所有方法都是公开的,接口中的常量都是public static和final,而且这些是不必说明的。

 

最后我们再来研究一下如何取得接口并调用它的服务。
var LiWei:IMyidol;
begin
 LiWei:=TAnders.Create; //Tanders的对象制定给了接口LiWei。
 LiWei.name('LiWei!');   //调用name方法。
这是种隐式的操作方式,再来看看更明确的写法:
var Anders:TAnders; LiWei:IMyidol;
begin
 Anders:=TAnders.Create; //生成对象。
 LiWei:= Imyidol(Anders);
 LiWei.name('LiWei!');
要说明的是,Anders必须支持Imyidol,不然报错。
还可以这样写:
var Anders:TAnders; LiWei:IMyidol;
const ILiWei:TGUID='{30DDAB80-81BF-11D8-A27B-930F92738A22}';
begin
 Anders:=TAnders.Create; //生成对象。
 if Anders.GetInterface(ILiWei,LiWei) then
 LiWei.name('LiWei!');
end;
在Java中的情形是一样的:
 Man man=new Man();
 man.name("Anders Hejlsberg!");   //这是类对象实现的方式。
 Myidol lw=new Man();
 lw.name("LiWei!");             //这是接口实现方式。
 
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值