程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 在Delphi中實現multi-delegate

在Delphi中實現multi-delegate

編輯:Delphi

 

 

不知從什麼時候開始,如果在文章標題中只提及一個技術術語,一般就默認表示它是用在.net中的技術了。所以我不得不在標題上加一個Delphi 字樣。

 

先為用Delphi 的朋友簡單介紹下delegate,如果你用過c# 什麼的,就可以跳過這部分了。delegate 就象個封裝了函數指針的對象,用delegate 對象“指向”一個函數,或事件句柄,然後在程序的其它地方調用它。雖然這種用法不很直觀,也一時難以想到什麼情況下需要這樣干,但這種方法看起來很靈活。

 

更進一步,如果一個delegate 對象能夠“指向”多個函數,然後在調用時,可以一次性調用所有它所指向的函數,就可以更靈活了。

 

這有點類似於觀察者模式,一個被觀察者,可以被多個觀察者收聽,當被觀察者需要告訴觀察者一些事情時,所有的觀察者都會收到。例如,在一個model 和view 分離的程序中,多個view 或controller 都可以收聽model 的同一個事件,這就是multi-delegate 了

 

我主要的工作是增加了invoke()方法,這樣就可以方便快速的調用了。它的用法是這樣:

 

 

 

TForm1 = class(TForm)

    Button1: TButton;

    Button2: TButton;

    procedure Button1Click(Sender: TObject);

    procedure Button2Click(Sender: TObject);

  private

  public

     { Public declarations }

     OnChange: TDelegate<TNotifyEvent>;

     procedure one(sender: TObject);

     procedure two(sender: TObject);

  end;

implementation

 

{$R *.dfm}

 

procedure TForm1.Button1Click(Sender: TObject);

begin

  OnChange.Add(one);

  onchange.Add(two);

  OnChange.invoke([sender]);

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

  fOnChange1: TDelegate<TProc<TObject>>;

begin

  fOnChange1.add(procedure(obj: TObject)

     begin

      showmessage(obj.ClassName);

      end);

  fOnChange1.invoke<TObject>(Sender);

end;

 

procedure TForm1.one(sender: TObject);

begin

  ShowMessage('one');

end;

 

procedure TForm1.two(sender: TObject);

begin

  ShowMessage('two');

end;

 

 

下面的內容是關於TDelegate<> 實現,首先是聲明部分:

 

 

 

type

IContainer<T> =interface

    procedure add(const handler: T);

    procedure remove(const handler: T);

    function GetEnumerator: TEnumerator<T>;

end;

 

CContainer<T> =class(TInterfacedObject, IContainer<T>)

private

    flist: TList<T>;

    constructor create;

    destructor Destroy; override;

    procedure add(const handler: T);

    procedure remove(const handler: T);

    function GetEnumerator: TEnumerator<T>;

end;

 

TDelegate<T> = record

private

    fContainer: IContainer<T>;

public

    procedure add(const handler: T);

    procedure remove(const handler: T);

    procedure invoke(const Args: array of TValue); overload;

    procedure invoke<TArg1>(Arg: TArg1); overload; experimental;

    function GetEnumerator: TEnumerator<T>;

end;

 

 

因為必須把它設計成無需創建的,所以TDelegate<> 必須是個結構類型。

 

TDelegate<> 擁有一個IContainer<> 接口對象,對TDelegate<> 的add(),remove(),實際都是交由CContainer<> 實現的,而在CContainer<> 內部,則是交由TList<> 實現的,當TDelegate<>的add()首次被調用時,它才創建fContainer 實例。由於是個接口,所以不需擔心它的釋放問題。

 

用戶不應看到IContainer<> 和CContainer<>,它們只是為TDelegate<> 服務的,按理說該把它定義為TDelegate<> 的內部類,但這樣編譯時會產生一個莫明的內部錯誤,至少在XE2 Update1 的中是這樣。你也可能會想到CContainer<> 可以用一個屬性委托實現IContainer<> 接口,我已經嘗試過了,Delphi 崩潰了。

 

下面是invoke() 的實現:

 

 

 

procedure TDelegate<T>.invoke(const Args: array of TValue);

var

    context: TRttiContext;

    method: TValue;

    methodType: TRttiInvokableType;

    p: TEnumerator<T>;

begin

p:=fContainer.GetEnumerator;

while p.MoveNext do begin

    method := context.GetType(p.ClassType).GetProperty('Current').GetValue(p);

    methodType := context.GetType(method.TypeInfo) as TRttiInvokableType;

    methodType.Invoke(method, Args);

    end;

p.Free;

end;

 

 

 

 

這裡用到了RTTI,但也只是勉強實現,比如,當T 為TMouseEvent 時就無能為力了,因為TMouseEvent 事件的參數中有集合類型,所以就沒法寫類似於fOnChange.invoke([Sender, [mbLeft]]); 這樣的代碼。

 

TDelegate<T> 中的T,除了是事件類型外,還可以是TProc<TArg1> 類型。這樣用起來就更靈活,比如這樣:

 

 

procedure TForm1.Button2Click(Sender: TObject);

 

var

 

OnChange: TDelegate<TProc<TObject>>;

 

begin

 

onChange.add(procedure(obj: TObject)

    begin

    showmessage(obj.ClassName);

    end);

 

onChange.invoke<TObject>(Sender);

 

end;

比之前的用法稍麻煩的是,這裡的invoke() 需要用<TArg1> 幫助指出參數的類型,目前我還沒有什麼辦法解決這個問題,所以這個版本只做了帶一個參數的invoke<TArg1>(arg1: TArg1)

 

 

 

procedure TDelegate<T>.invoke<TArg1>(Arg: TArg1);

var

    context: TRttiContext;

    p: TEnumerator<T>;

    pt: Pointer;

    v: TValue;

    aProc: TProc<TArg1>;

begin

p:=fContainer.GetEnumerator;

while p.MoveNext do begin

    v := context.GetType(p.ClassType).GetProperty('Current').GetValue(p);

    v.ExtractRawDataNoCopy(@pt);

    aProc:=TProc<TArg1>(pt);

    aProc(arg);

    end;

p.Free;

end;

 

 

先是把fList 的元素值轉換為指針,然後又轉成了TProc<TArg1>,編譯後這段代碼沒有打上綠色的小點,所以這段代碼沒法被斷點調試,但它確實有效。你也可以用下面這段代碼代替onChange.invoke<TObject>(Sender);

 

 

 

var

    p: TProc<TObject>;

begin

for p in fOnChange do

    p(Sender);

end;

以上全部代碼在這裡可以下載

http://www.BkJia.com/uploadfile/2011/1007/20111007031616193.rar

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved