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