Delphi的基于接口(IInterface)的多播监听器模式(观察者模式 )

Posted findumars

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.
技术图片

 

https://www.cnblogs.com/hezihang/p/6083555.html

以上是关于Delphi的基于接口(IInterface)的多播监听器模式(观察者模式 )的主要内容,如果未能解决你的问题,请参考以下文章

04.Delphi通过接口IInterface实现多重继承

Delphi 接口-接口和多重继承

delphi xe10 手机程序事件服务操作退出键操作

基于Delphi的接口编程入门

将带有接口的EqualityComparer作为通用参数调用是否有意义

tcpdump使用