本文來自: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.