我們知道Delphi的每個對象可以包含多個Property,Property中可以是方法,例如TButton.OnClick屬性。Delphi提供的僅僅是
一對一的設置,無法直接讓TButton.OnClick去調用多個方法,而Java中采用Listener模式有類似AddListener方法提供多播。
Delphi多播的思想源於的Blog:http://blogs.embarcadero.com/abauer/2008/08/15/38865,
cnWizard的武稀松大俠在此思想基礎上實現了Win32的Delphi多播機制見:http://www.raysoftware.cn/?p=44#comment-2442,並且應用於cnWizard;
開源項目DSharp實現了更加完整的多播機制,可提供基於接口的多播,見:https://code.google.com/p/delphisorcery/
本人希望借鑒前人的基礎上,實現一個對象的事件多播代理,即TEventAgent是一個TObject的事件多播代理器,將一個TObject傳給TEventAgent後, TEventAgent掃描TObject所有事件,並為每個事件提供多播功能。
下面程序是一個簡單示例,引用了 DSharp.Core.Events.pas單元,並在Delphi XE3 測試成功.
TEventLinker=(DSharp.Core.Events.TEvent) //
MethodAdded( Method: TMethod); MethodRemoved( Method: TMethod); Notify(Sender: TObject; Action: System.Generics.Collections.TCollectionNotification);
Destroy;
TEventAgent= //
FNameList:TDictionary<String, TEventLinker> Prepare;
Create(aOwner:TObject); Destroy; AddEventNotifier(EventName: String; NotifierMethod: TMethod);; // RemoveEventNotifier(EventName: String; NotifierMethod: TMethod);; //
Create(LinkedPrpt.PropType^, FLinkedObject:= FLinkedProperty:= FEventName:= FOriginal:= Assigned(FOriginal.Data) Assigned(FOriginal.Code) Add(FOriginal); //
TEventLinker.MethodAdded(
TEventLinker.MethodRemoved(
TEventLinker.Notify(Sender: TObject;
FNameList.TryGetValue(EventName, V)
V.IndexOf(NotifierMethod)<
Item: TPair<String, TEventLinker>
Item FNameList
Assigned(FPropList)
FNameList:=TDictionary<String, TEventLinker> FOwner:=
Result:=
Result:=
N:= i := N-
FPropList^[i].PropType^.Kind = tkMethod
FPropList[i].GetProc= Linker:= Linker.FEventName:=
FNameList.TryGetValue(EventName, V)
.
測試程序演示一個TButton被事件多播代理,其OnClick,OnMouseDown均有3個多播方法。
測試程序:
TForm1 =
Memo1.Lines.Add(
Memo1.Lines.Add(Format(
Memo1.Lines.Add(
Memo1.Lines.Add(
FAgent:= V:= FAgent.AddEventNotifier( V:= FAgent.AddEventNotifier( M:= FAgent.AddEventNotifier( M:= FAgent.AddEventNotifier(
Memo1.Lines.Add(
Memo1.Lines.Add(
.
測試程序dfm文件
Left =
Top =
Caption =
ClientHeight =
ClientWidth =
OnCreate = Left =
Top =
Width =
Height =
Caption =
OnClick = OnMouseDown =
Left =
Top =
Width =
Height =
Lines.Strings =
我的多播代理機制原理是,將所代理對象的所有事件指向代理器對應的函數,由此函數再以此調用多個回調函數。
1.當所代理事件沒有任何事件回調時,多播代理不會修改事件函數指針,原對象此事件回調仍然為nil,
2.當所代理事件已經有事件回調函數指針,多播代理會將自己替換原函數指針,並且將原函數指針加入多播列表中.
我的多播機制有如下特點:
1.兼容Delphi的事件回調機制,因此對於老的程序,不用怎麼修改,就能被回調多個函數,實現多播。
2.此多播機制不限於界面對象,可代理任何對象,只要此對象有放入public或published的事件property屬性,均被自動代理,無所謂其傳入的參數是什麼類型及有多少個。
3.用戶的對象如果需要多播功能,僅需要按照單個事件模式設計即可,多播代理自動幫他實現多播。
再舉例1:
比如我們網絡通訊假設用的是TTcpClient,從服務器接收數據。接收來的數據進行處理,處理過程有很多,比如有的模塊需要存盤到文件,有的處理模塊進行數據轉發,有的模塊需要進行解碼分析。
如果使用多播,則可以簡單的方法實現。
假如原來的網絡程序僅實現了數據存儲功能,需要增加解碼處理功能,我們不需要修改原來的程序,增加解碼模塊即可:
1.新建一個DataModule, 放上一個TTcpClient,設置要連接的服務器端口地址
unit Unit2;
interface
uses
System.SysUtils, System.Classes, Web.Win.Sockets, utObjEventAgent;
type
TDataModule2 = class(TDataModule)
TcpClient1: TTcpClient;
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
FLink:TEventAgent;
end;
var
DataModule2: TDataModule2;
implementation
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TDataModule2.DataModuleCreate(Sender: TObject);
begin
FLink:=TEventAgent.Create(TcpClient1);
TcpClient1.Active:=True;
end;
procedure TDataModule2.DataModuleDestroy(Sender: TObject);
begin
FLink.Free;
end;
end.
2.接著,只需在不同的模塊去接收你的數據,例如數據存儲模塊:
unit Unit3;
interface
uses utObjEventAgent, Unit2, Classes, Web.Win.Sockets;
type
TPersistModule=class
protected
FStream:TFileStream;
private
procedure OnDataReceive(Sender: TObject; Buf: PAnsiChar; var DataLen: Integer);
public
constructor Create;
destructor Destroy;override;
end;
implementation
{ TPersistModule }
constructor TPersistModule.Create;
var
V:TSocketDataEvent;
begin
inherited Create;
FStream:=TFileStream.Create('C:\test.dat', fmCreate);
V:= Self.OnDataReceive;
DataModule2.FLink.AddEventNotifier('OnReceive', TMethod(V));
end;
destructor TPersistModule.Destroy;
var
V:TSocketDataEvent;
begin
V:= Self.OnDataReceive;
DataModule2.FLink.RemoveEventNotifier('OnReceive', TMethod(V));
FStream.Free;
inherited;
end;
procedure TPersistModule.OnDataReceive(Sender: TObject; Buf: PAnsiChar;
var DataLen: Integer);
begin
FStream.Write(Buf^, DataLen);
end;
end.
3.數據解碼模塊
unit Unit4;
interface
uses utObjEventAgent, Unit2, Classes, Web.Win.Sockets, utDecoder;
type
TDecodeModule=class
protected
FDecoder:TDecoder;
private
procedure OnData(Sender: TObject; Buf: PAnsiChar; var DataLen: Integer);
public
constructor Create;
destructor Destroy;override;
end;
implementation
{ TDecodeModule }
constructor TDecodeModule.Create;
var
V:TSocketDataEvent;
begin
inherited Create;
FDecoder:=TDecoder.Create
V:= Self.OnData;
DataModule2.FLink.AddEventNotifier('OnReceive', TMethod(V));
end;
destructor TDecodeModule.Destroy;
var
V:TSocketDataEvent;
begin
V:= Self.OnData;
DataModule2.FLink.RemoveEventNotifier('OnReceive', TMethod(V));
Fdecoder.Free;
inherited;
end;
procedure TDecodeModule.OnData(Sender: TObject; Buf: PAnsiChar;
var DataLen: Integer);
begin
FDecoder.Decode(Pointer(Buf), DataLen);
end;
end.
再舉例2:
借用 “Delphi 實現事件偵聽與觸發”的例子:
const
evtDataChanged = 'evtDataChanged';
//數據處理類, 用於提供數據
TOnData=procedure( Name, City, CellPhone:String; Age: Integer ) of Object;
TNwDataClass = class( TObject)
private
FOnData:TOnData;
public
Link:TEventAgent;
constructor Create;
destructor Destroy;override;
procedure AddData( Name, City, CellPhone:String; Age: Integer );
property OnData:TOnData read FOnData write FOnData;
end;
//界面顯示類
TNwInterface = class( TForm )
procedure FormCreate( Sender: TObject );
procedure FormDestroy( Sender: TObject );
protected
procedure OnEvent( Name, City, CellPhone:String; Age: Integer );
procedure OnEvent2( Name, City, CellPhone:String; Age: Integer );
public
procedure AddDataToList( Name, City, CellPhone:String; Age: Integer);
procedure AddDataToFile( Name, City, CellPhone:String; Age: Integer );
end;
// TNwDataClass 應該有一個全局的實例, 用於提供數據. 在下面的代碼中, 就以
// instanceDataClass 為這個實例
implementation
{ TNwDataClass }
constructor TNwDataClass.Create;
begin
inherited Create;
Link:=TEventAgent.Create(Self);
end;
destructor TNwDataClass.Destroy;
begin
Link.Free;
inherited;
end;
procedure TNwDataClass.AddData( Name, City, CellPhone:String; Age: Integer );
begin
//數據處理代碼,忽視Link的存在
if Assigned(FOnData) then FOnData(Name, City, CellPhone, Age);
end;
{ TNwInterface }
procedure TNwInterface.FormCreate( Sender: TObject );
var V:TOnData;
begin
V:= Self.OnEvent;
instanceDataClass.Link.AddEventNotifier('OnData', TMethod(V));
V:= Self.OnEvent2;
instanceDataClass.Link.AddEventNotifier('OnData', TMethod(V));
end;
procedure TNwInterface.FormDestroy( Sender: TObject );
var V:TOnData;
begin
V:= Self.OnEvent;
instanceDataClass.Link.RemoveEventNotifier('OnData', TMethod(V));
V:= Self.OnEvent2;
instanceDataClass.Link.RemoveEventNotifier('OnData', TMethod(V));
end;
procedure TNwInterface.OnEvent( Name, City, CellPhone:String; Age: Integer );
begin
AddDataToList( Name, City, CellPhone, Age);
end;
procedure TNwInterface.OnEvent2( Name, City, CellPhone:String; Age: Integer );
begin
AddDataToFile( Name, City, CellPhone, Age);
end;
procedure TNwInterface.AddDataToList( Name, City, CellPhone:String; Age: Integer );
begin
//用於處理顯示數據的代碼.
end;
procedure TNwInterface.AddDataToFile( Name, City, CellPhone:String; Age: Integer );
begin
//用於保存數據的代碼.
end;