From Code6421
浅谈Interface
Write By code6421
多重继承
OOP 的重点在于继承,封装,多型等概念上,其中以继承最受争议,尤其是多重继承与单一继承,到目前为止,
至少在实作上许多语言都选择了单一继承,原因不外乎多重继承会将整个对象架构复杂化,为了保留多重继承的特性,
Interface(界面) 的概念就成为最好的选择,标准的OO 继承课题就是父母与子女之间的关系,子女继承了父与母的的特性,
此为多重继承,但我们都知道,现实上子女并不会拥有父母的所有特性及能力,但在多重继承概念上,子可以向上转型为父,
也可以向上转型为母,而这就是多重继承受争议的地方.
图:多重继承
由上面的图我们可以发现,子同时继承了父与母两个类别,也就是说子拥有了父与母类别所有的
特性,让我们以一个较简单的方式来说,当父类别拥有抽烟的特性,那在多重继承的观念下
,子类别必然也有抽烟的特性,但我们都知道这并不是绝对的.因此我们需要让子类别选择是否会抽烟,基于这个理由,
我们得把父类别的抽烟特性定义成可覆载,这样子类别才能选择是否会抽烟,
如果这类特性不多的话还好,但多的话就很烦人了,所以多重继承下的结果,必定是很沉重的.
多重继承的替代品 - Interface
Interface 以支持某种能力(或拥有某种能力) 为主体来取代多重继承,以上面的类别来定义的话,就如下图:
图:以Interface 实作取代继承
从上图来看,你可以发现子直接继承人类别,而不是继承父或母,那子类别如何拥有父与母的特性呢? 例如子类别要拥有抽烟的能力?
上图中我们称之为子类别继承了人类别并实作了抽烟这个接口,我们也可以说子是个人,拥有抽烟的能力,
这样的做法是否比上面的多重继承更符合现实呢? 呵,我把这个问题留给你,我可不想再一次陷入论战中,
回到Interface,基本上Interface 也拥有了继承特性,你可以继承抽烟这个Interface,并加入新的特性
图:Interface 继承
ㄜ…我知道这不太雅,呵,你就将就一下吧
因此我们重新定义一下父母子的关系
图:实作继承的Interface
Interface 继承与Class 继承是差不多的,只是Interface 继承了定义而非实体
类别也可以实作多个Interface,例如下图:
DELPHI(PASCAL) 与Interface
基本上DELPHI 支援Interface 操作,但在DELPHI 6 之前的操作较不直觉,因此容易造成DELPHI 对Interface 支持不足的假象,
庆幸的是在DELPHI 6 中这个问题已经被解决了,这也使得Interface 成为WebSnap 最重要的部份,同时也在VCL 中有相当重的戏份,
下面是你最常看到的Interface 运用:
IMyInterface=interface
['{FE5A34E5-21AB-4120-971B-FDC3241AD55D}']
function SayHello:string;
end;
TMyObject=class(TInterfacedObject,IMyInterface)
function SayHello:string;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure DoSayHello(Intf:IMyInterface);
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
function TMyObject.SayHello:string;
begin
Result:='Hello';
end;
procedure TForm1.DoSayHello(Intf:IMyInterface);
begin
ShowMessage(Intf.SayHello);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Obj:TMyObject;
Intf:IMyInterface;
begin
Obj:=TMyObject.Create;
Intf:=(Obj as IMyInterface);
DoSayHello(Intf);
end;
OK,我想这个范例大家都看过了,接下来我们变点不一样的
type
IMyInterface=interface
['{FE5A34E5-21AB-4120-971B-FDC3241AD55D}']
function SayHello:string;
end;
TMyObject=class(TEdit,IMyInterface)
function SayHello:string;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure DoSayHello(Intf:IMyInterface);
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
function TMyObject.SayHello:string;
begin
Result:='Hello';
end;
procedure TForm1.DoSayHello(Intf:IMyInterface);
begin
ShowMessage(Intf.SayHello);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Obj:TMyObject;
Intf:IMyInterface;
begin
Obj:=TMyObject.Create(Self);
Intf:=(Obj as IMyInterface);
DoSayHello(Intf);
end;
嘿! 我可没要你照着打哦,这样是不会通过编译的,你得变成这样才行(在DELPHI 5).
procedure TForm1.Button1Click(Sender: TObject);
var
Obj:TMyObject;
Intf:IMyInterface;
begin
Obj:=TMyObject.Create(Self);
Obj.GetInterface(IMyInterface,Intf);
DoSayHello(Intf);
Obj.Free;
end;
GetInterface 是用来取得我们想要的Interface,基本上它会传回一个Boolean 代表是否取得了Interface,
如果你在DELPHI 6 的话,之前未修改过的那一个版本就可以通过编译器,所以啦,DELPHI 6 还是进步了.
其实在DELPHI 6 中正规的写法是这样
procedure TForm1.Button1Click(Sender: TObject);
var
Obj:TMyObject;
Intf:IMyInterface;
begin
Obj:=TMyObject.Create(Self);
if Supports(Obj,IMyInterface,Intf) then
DoSayHello(Intf);
Obj.Free;
end;
如果你在DELPHI 5 中这样写也可以,不过你会发现Supports 除了继承至TinterfacedObject 之外的对象都会传回False,
这是因为DELPHI 5 的的TComponent 并未实作Iunknown(至少在明定上没有),但在DELPHI 6中,TComponent 实做了相当于Iunknown的Iinterface,
所以如果你想要在你的程序中完整运用Interface,建议你还是用DELPHI 6 会较为直觉.
用DELPHI 5 的话还可使用下面的方法来通过编译器
TMyObject=class(TEdit,IMyInterface,IUnknown)
这样你就可以使用as 来转型,可是如果你要使用Supports 的话,Compiler 会丢出一个错误,因此还是使用GetInterface 来的方便一点!
实作多个Interface
做这件事是很简单的,一个类别可以实做一个已上的Interface,这我想你一定早就知道了,
因此在这里我提一下有关实作多个Interface 时的运用.
转型规则
一个类别实作了一个已上的Interface 时,例如X类别实做了A,B,C 三个Interface,因此你可以透过X 取得 A,
同样的,你也可以透过A 取得 B或透过A 取得C,这在COM 中有很详细的定义:
Symmetric(对称) 当你透过A 成功取得 B,那么你也可以透过B 成功取得A
Transitive(递移) 当你透过A 成功取得B,且透过B 成功取得C,那么你也可以透过A 成功取得C.
Reflexive(反身) 使用A 查询 A 必定是成功的.
Delegation(代理)
从DELPHI 4 开始,OO PASCAL 就支持Interface Delegation,这种技术使得实作Interface 变的更有弹性,你可以从下面的范例中看出端倪
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
IMyInterface=interface
['{2E173B2D-6BE9-4519-8E5F-6DEF400335EC}']
function SayHello:string;
end;
IMyInterface2=interface
['{3FD6CFDF-E028-4FD6-9834-299404C15FFF}']
function SayHello2:string;
end;
TMyObject2=class(TInterfacedObject,IMyInterface2)
function SayHello2:string;
end;
TMyObject=class(TInterfacedObject,IMyInterface,IMyInterface2)
private
FDelgateObj:TMyObject2;
public
property DelgateObj:TMyObject2 read FDelgateObj implements IMyInterface2;
constructor Create;
function SayHello:string;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TMyObject.Create;
begin
FDelgateObj:=TMyObject2.Create;
end;
function TMyObject2.SayHello2:string;
begin
Result:='I am Object2';
end;
function TMyObject.SayHello:string;
begin
Result:='I am Object1';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyObject:TMyObject;
Intf1:IMyInterface;
Intf2:IMyInterface2;
begin
MyObject:=TMyObject.Create;
if Supports(MyObject,IMyInterface,Intf1) then
ShowMessage(Intf1.SayHello);
if Supports(MyObject,IMyInterface2,Intf2) then
ShowMessage(Intf2.SayHello2);
end;
end.
重点就在篮色字及红色字的部份,你可以发现我们的TMyObject实作了两个Interface,但你在里面却只找到IMyInterface的定义,
这是因为我们运用了Delgation 将IMyInterface2 导向TMyObject2,而它正是实作IMyInterface2 的类别,这种技术使得主类别简洁许多
,同时也带出了另一种运用:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
IMyInterface=interface
['{2E173B2D-6BE9-4519-8E5F-6DEF400335EC}']
function SayHello:string;
end;
IMyInterface2=interface
['{3FD6CFDF-E028-4FD6-9834-299404C15FFF}']
function SayHello2:string;
end;
TMyObject2=class(TInterfacedObject,IMyInterface2)
function SayHello2:string;
end;
TMyObject=class(TInterfacedObject,IMyInterface,IMyInterface2)
private
FDelgateObj:IMyInterface2;
public
property DelgateObj:IMyInterface2 read FDelgateObj write FDelgateObj implements IMyInterface2;
constructor Create;
function SayHello:string;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TMyObject.Create;
begin
FDelgateObj:=Nil;
end;
function TMyObject2.SayHello2:string;
begin
Result:='I am Object2';
end;
function TMyObject.SayHello:string;
begin
Result:='I am Object1';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyObject:TMyObject;
MyObject2:TMyObject2;
Intf1:IMyInterface;
Intf2:IMyInterface2;
begin
MyObject:=TMyObject.Create;
MyObject.FDelgateObj:=TMyObject2.Create;
if Supports(MyObject,IMyInterface,Intf1) then
ShowMessage(Intf1.SayHello);
if Supports(MyObject,IMyInterface2,Intf2) then
ShowMessage(Intf2.SayHello2);
end;
end.
上面这个范例告诉我们,我们可以指派任何实作了IMyInterface2对象给TmyObject,这就是上面所说的另一种运用.
Property In Interface
DELPHI 6 中的说明档标示这个是新功能,但事实上,DELPHI 5 就有这个能力了,没有DELPHI 4,所以不知道她有没有,
基本上这是让你可以在Interface 中宣告Property,我们用一个范例开始:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
IMyInterface=interface
['{2E173B2D-6BE9-4519-8E5F-6DEF400335EC}']
function GetSayHello:string;
property SayHello:string read GetSayHello;
end;
IMyInterface2=interface
['{3FD6CFDF-E028-4FD6-9834-299404C15FFF}']
function GetSayHello2:string;
property SayHello2:string read GetSayHello2;
end;
TMyObject2=class(TInterfacedObject,IMyInterface2)
function GetSayHello2:string;
end;
TMyObject=class(TInterfacedObject,IMyInterface,IMyInterface2)
private
FDelgateObj:IMyInterface2;
public
property DelgateObj:IMyInterface2 read FDelgateObj write FDelgateObj implements IMyInterface2;
constructor Create;
function GetSayHello:string;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TMyObject.Create;
begin
FDelgateObj:=Nil;
end;
function TMyObject2.GetSayHello2:string;
begin
Result:='I am Object2';
end;
function TMyObject.GetSayHello:string;
begin
Result:='I am Object1';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyObject:TMyObject;
MyObject2:TMyObject2;
Intf1:IMyInterface;
Intf2:IMyInterface2;
begin
MyObject:=TMyObject.Create;
MyObject.FDelgateObj:=TMyObject2.Create;
if Supports(MyObject,IMyInterface,Intf1) then
ShowMessage(Intf1.SayHello);
if Supports(MyObject,IMyInterface2,Intf2) then
ShowMessage(Intf2.SayHello2);
end;
end.
我们在Interface 将SayHello,SayHello2 定义为property,而实作这两个Interface 只需实作Get Method就可以了,
这是否使得Interface 的运用又更方便了呢?
DELPHI 6 的Interface 支持已经相当完备了,如果你正巧有DELPHI 6,也正巧要开发软件,
使用Interface 将会使你的软件有更高的延展性,当然! 好好规划也是很重要的.
DELPHI 6 的Interface 与 Variant
DELPHI 6 支持Custom Variants,这是一个非常有用的特色,但它和Interface 有何关系呢?
答案并不在Custom Variants 身上,而是在DELPHI 6 重新实作Variants这件事上,因为这个动作,使得我们下面的程序得以正常运作:
procedure TForm1.Button1Click(Sender: TObject);
var
MyObject:TMyObject;
MyObject2:TMyObject2;
Intf1:IMyInterface;
Intf2:IMyInterface2;
V:Variant;
begin
MyObject:=TMyObject.Create;
MyObject.FDelgateObj:=TMyObject2.Create;
V:=(MyObject as IInterface);
if Supports(V,IMyInterface,Intf1) then
ShowMessage(Intf1.SayHello);
if Supports(V,IMyInterface2,Intf2) then
ShowMessage(Intf2.SayHello2);
end;
这段程序代码隐含着一个意义,就是你可以把任何对象转成IInterface 塞进Variant 中,那对你有何帮助呢? 呵! 我不知道,你慢慢想吧!
给DELPHI 5 使用者
是的,上面所谈的技巧大多可以用DELPHI 5 达到,只是你必须要做一些额外的工作,
例如你可以将Iunknown,Idispatch 指派给Variant 后传递,但传送的如果是TComponent 呢?
你必需想办法将Iunknown 对应到TComponent 上,这就是额外的工作,DELPHI 6 提供我们更直觉的方式,
或许这正是升级DELPH 6 的好借口
最后......................
文中的程序代码如果可以运作的话,那是我写的,如果不能的话,那我不知道是谁写的.