Delphi采用接口方式设计模块,可以降低模块之间的耦合,便于扩展和维护。本文提供一个实现基于接口(IInterface)方式的监听器模式(观察者模式、订阅者模式),实现一个自动多播器。
下面程序在Berlin下测试通过,其他Delphi版本未测试,未进行跨平台测试(应该可以支持)
1.prepare
在观察者模式中采用接口,可以将相关函数汇合为接口。
举例:假设我们窗口有一个TTreeView,用于显示应用中的对象,用户通过点击TreeView中的不同对象,切换其他多个不同窗口中的显示内容。
在传统的方式下,维护一个通知列表,可以采用TreeView.OnChange事件中调用所有通知,然后处理如下:(也可采用多播方式,详见:)
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