注册 | 登录 忘记密码? 51cto首页 | 博客 | 论坛 | 招聘
热点文章 linux服务---DHCP
 帮助

淺談Interface


2007-09-17 20:11:37
 标签:Interface   [推送到技术圈]

多重繼承
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 實做了相當於IunknownIinterface,
所以如果你想要在你的程式中完整運用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 的好藉口 :) 
 

最後......................

文中的程式碼如果可以運作的話,那是我寫的,如果不能的話,那我不知道是誰寫的. :)
 




    文章评论
 
 

发表评论

昵   称:
验证码:  点击图片可刷新验证码  博客过2级,无需填写验证码
内   容: