Delphi的基于接口(IInterface)的多播监听器模式(观察者模式 )
本文来自:http://www.cnblogs.com/hezihang/p/6083555.html
Delphi采用接口方式设计模块,可以降低模块之间的耦合,便于扩展和维护。本文提供一个实现基于接口(IInterface)方式的监听器模式(观察者模式、订阅者模式),实现一个自动多播器。
下面程序在Berlin下测试通过,其他Delphi版本未测试,未进行跨平台测试(应该可以支持)
1.prepare
在观察者模式中采用接口,可以将相关函数汇合为接口。
举例:假设我们窗口有一个TTreeView,用于显示应用中的对象,用户通过点击TreeView中的不同对象,切换其他多个不同窗口中的显示内容。
在传统的方式下,维护一个通知列表,可以采用TreeView.OnChange事件中调用所有通知,然后处理如下:(也可采用多播方式,详见:http://www.cnblogs.com/hezihang/p/3299481.html)
procedure TForm2.TreeView1Change(Sender: TObject; Node: TTreeNode); var L:TTVChangedEvent; begin for L in FList do //FList:TList<TTVChangedEvent> L(Sender, Node); end;
显然采用传统方式,各窗口都需要uses TreeView所在窗口。
另外,如果TreeView所在窗口还有其他事件需要对多个外部窗口或对象进行通知,
则再需要建立一个通知列表。
采用事件方式,需要自己维护一个或多个通知列表,同时各个使用事件的单元都需要引用事件源的单元。
2.
如果我们采用接口方式,将所有事件包含进去,由TreeView所在窗口去调用:
type {$M+} ICurrentStateObserver=interface ['{20E8D6CB-3BCF-4DAE-A6CE-FEA727133C57}'] procedure OnCurrentObjectChange(CurObj:Pointer); procedure OnDataReceive(Buf:Pointer; Size:Integre); procedure OnResize(W, H:Integer); end; {$M-}
注意实现自动观察者的接口必须打开RTTI,{$M+}
然后,只需要如下调用,就可以让所有监听者(观察者)的OnResize被调用(接口内所有方法均可被调用):
procedure TForm2.FormResize(Sender: TObject); begin CurrentStateDispatcher.Source.OnResize(Width, Height); end;
其中:
(1).
CurrentStateDispatcher.Source:ICurrentStateObserver
是一个虚拟接口,也是ICurrentStateObserver类型。调用此接口内的方法,就自动调用所有观察者所对应方法。
这样我们只需要调用
CurrentStateDispatcher.Source.OnCurrentObjectChange(...);
CurrentStateDispatcher.Source.OnDataReceive(...);
CurrentStateDispatcher.Source.OnResize(...);
就可以实现所有观察者的调用。
(2).
CurrentStateDispatcher:IInterfaceObservable<ICurrentStateObserver>
IInterfaceObservable<ICurrentStateObserver>是一个实现ICurrentStateObserver的多播监听器模式(观察者模式)的接口:
IInterfaceObservable < T: IInterface >= interface procedure AddObserver(const aListener: T); procedure RemoveObserver(const aListener: T); function GetSource: T; property Source: T read GetSource; end;
AddObserver是添加监听者,RemoveObject是删除观察者
Source就是前面提到的用于多播调用的虚拟接口。
(3).在使用模式的对象中声明:
TForm2=class(TForm) .... private FCurrentStateDispatcher:IInterfaceObservable<ICurrentStateObserver>; public property CurrentStateDispatcher:IInterfaceObservable<ICurrentStateObserver> read FCurrentStateDispatcher; end;
3.
下面我们看一个完整的使用例子:
uMainForm.pas
unit uMainForm; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs , uInterfaceObservable, Vcl.StdCtrls, Vcl.ComCtrls; type {$M+} ITestObserver=interface ['{FE7F7C11-13BC-472A-BB7A-6536E20BCEDD}'] procedure OnClick(Sender:TObject); procedure OnResize(Sender:TObject; X, Y:Integer); end; {$M-} TForm2=class; TObserver1=class(TInterfacedObject, ITestObserver) F:TForm2; procedure OnClick(Sender:TObject); procedure OnResize(Sender:TObject; W, H:Integer); constructor Create(Owner:TForm2); end; TObserver2=class(TInterfacedObject, ITestObserver) F:TForm2; procedure OnClick(Sender:TObject); procedure OnResize(Sender:TObject; W, H:Integer); constructor Create(Owner:TForm2); end; TForm2 = class(TForm) Memo2: TMemo; procedure FormClick(Sender: TObject); procedure FormResize(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } FTestDispatcher:IInterfaceObservable<ITestObserver>; public { Public declarations } property TestDispatcher:IInterfaceObservable<ITestObserver> read FTestDispatcher; end; var Form2: TForm2; implementation {$R *.dfm} procedure TForm2.FormClick(Sender: TObject); begin FTestDispatcher.Source.OnClick(Sender); end; procedure TForm2.FormCreate(Sender: TObject); begin FTestDispatcher:=TDioInterfaceDispatcher<ITestObserver>.Create; FTestDispatcher.AddObserver(TObserver1.Create(Self)); FTestDispatcher.AddObserver(TObserver2.Create(Self)); end; procedure TForm2.FormDestroy(Sender: TObject); begin FTestDispatcher:=nil; end; procedure TForm2.FormResize(Sender: TObject); var // i:Integer; // T:LongWord; W, H:Integer; begin W:=Width; H:=Height; // T:=GetTickCount; // for i := 0 to 1000000 do TestDispatcher.Source.OnResize(Sender, W, H); // ShowMessage(IntToStr(GetTickCount- T)); end; { TObserver1 } constructor TObserver1.Create(Owner: TForm2); begin F:=Owner; end; procedure TObserver1.OnClick(Sender: TObject); begin F.Memo2.Lines.Add('TObserver1.OnClick'); end; procedure TObserver1.OnResize(Sender: TObject; W, H:Integer); begin F.Memo2.Lines.Add(Format('TObserver1.OnResize:%d, %d', [W, H])); end; { TObserver2 } constructor TObserver2.Create(Owner: TForm2); begin F:=Owner; end; procedure TObserver2.OnClick(Sender: TObject); begin F.Memo2.Lines.Add('TObserver2.OnClick'); end; procedure TObserver2.OnResize(Sender: TObject; W, H:Integer); begin F.Memo2.Lines.Add(Format('TObserver2.OnResize:%d, %d', [W, H])); end; end.
uMainForm.dfm
object Form2: TForm2 Left = 0 Top = 0 Caption = 'Form2' ClientHeight = 309 ClientWidth = 643 OnClick = FormClick OnCreate = FormCreate OnDestroy = FormDestroy OnResize = FormResize TextHeight = 13 object Memo2: TMemo Left = 0 Top = 152 Width = 643 Height = 157 Align = alBottom TabOrder = 0 end end
4.
下面是uInterfaceObservable.pas
unit uInterfaceObservable; interface uses System.Generics.Collections, System.TypInfo, System.Rtti; type IInterfaceObservable < T: IInterface >= interface procedure AddObserver(const aListener: T); procedure RemoveObserver(const aListener: T); function GetSource: T; property Source: T read GetSource; end; TDioInterfaceDispatcher<T: IInterface> = class(TInterfacedObject, IInterfaceObservable<T>) protected class var FTypeInfo: PTypeInfo; class var FMethods: TArray<TRttiMethod>; class var FIID: TGUID; class constructor Create; protected FList: TList<T>; FVirtualSource, FSource: T; FVirtualInterface: TVirtualInterface; FEvents: TObjectList<TList<TMethod>>; procedure MethodInvoke(Method: TRttiMethod; const Args: TArray<TValue>; out Result: TValue); public procedure AddObserver(const aListener: T); procedure RemoveObserver(const aListener: T); function GetSource: T; constructor Create; destructor Destroy; override; property Source: T read FSource; end; implementation uses System.SysUtils; { TDioDispatcher<T> } procedure TDioInterfaceDispatcher<T>.AddObserver(const aListener: T); type TVtable = array [0 .. 3] of Pointer; PVtable = ^TVtable; PPVtable = ^PVtable; var i: Integer; M: TMethod; P: Pointer; begin FList.Add(aListener); P:=IInterface(aListener); // P := IInterfaceGetObject(aListener).GetObject; for i := 0 to FEvents.Count - 1 do begin // 3 is offset of Invoke, after QI, AddRef, Release M.Code := PPVtable(P)^^[3 + i ] ; M.Data := P; FEvents[i].Add(M); end; if FList.Count=1 then FSource:=aListener else FSource:=FVirtualSource; end; procedure TDioInterfaceDispatcher<T>.MethodInvoke(Method: TRttiMethod; const Args: TArray<TValue>; out Result: TValue); var L:TList<TMethod>; M:TMethod; i:Integer; begin L:=FEvents[Method.VirtualIndex-3]; i:=0; while i<L.Count do begin M:=L[i]; Args[0]:=M.Data; System.Rtti.Invoke(M.Code, Args, Method.CallingConvention, nil); if (M=L[i]) then Inc(i); end; end; constructor TDioInterfaceDispatcher<T>.Create; var i: Integer; LMethod: TRttiMethod; E: TList<TMethod>; S:String; begin inherited Create; FEvents := TObjectList<TList<TMethod>>.Create(True); FList := TList<T>.Create; FVirtualInterface := TVirtualInterface.Create(FTypeInfo); FVirtualInterface.OnInvoke := Self.MethodInvoke; FVirtualInterface.QueryInterface(FIID, FVirtualSource); Assert(Assigned(FVirtualSource), '未找到接口' + GUIDToString(FIID)); FSource:=FVirtualSource; for i := 0 to High(FMethods) do begin E := TList<TMethod>.Create;//TEvent.Create(LMethod, FTypeInfo, i); FEvents.Add(E); end; end; class constructor TDioInterfaceDispatcher<T>.Create; var LType: TRttiType; FContext: TRttiContext; begin FTypeInfo := TypeInfo(T); LType := FContext.GetType(FTypeInfo); FIID := TRttiInterfaceType(LType).GUID; FMethods := LType.GetMethods(); //Assert(Length(FMethods) <= 30, '只能分发30个以内函数的接口!'); end; destructor TDioInterfaceDispatcher<T>.Destroy; var i: Integer; begin FSource := nil; FVirtualSource:=nil; FVirtualInterface := nil; FList.DisposeOf; FEvents.DisposeOf; inherited; end; function TDioInterfaceDispatcher<T>.GetSource: T; begin Result := FSource; end; procedure TDioInterfaceDispatcher<T>.RemoveObserver(const aListener: T); var N, i: Integer; begin N := FList.IndexOf(aListener); if N >= 0 then begin for i := 0 to FEvents.Count - 1 do FEvents[i].Delete(N); end; FList.Remove(aListener) end; end.