Delphi-based Interface (IInterface) multicast listener mode (observer pattern)

This article comes from: http: //www.cnblogs.com/hezihang/p/6083555.html

Interface module using Delphi design can reduce the coupling between the module, to facilitate maintenance and extensions. This article provides an implementation of listener mode (observer mode, a subscriber model) based on the interface (IInterface) way to achieve an automatic multicast device.

The following test program in Berlin by other Delphi versions not tested, not cross-platform test (should be able to support)

1.prepare

Interface using the observer pattern, the correlation function may merge the interface.

Example: Suppose we have a window TTreeView, objects for display applications, the user clicks TreeView different objects, a plurality of different switch the display contents of other windows.

In the traditional way, maintains a notification list, TreeView.OnChange event can be used to call all notifications, and then processed as follows :( can also be used in multicast mode, see: http: //www.cnblogs.com/hezihang/p /3299481.html)

Copy the code
procedure TForm2.TreeView1Change(Sender: TObject; Node: TTreeNode);
var
  L:TTVChangedEvent;
begin
  for L in FList do  //FList:TList<TTVChangedEvent>
    L(Sender, Node);
end;
Copy the code

Obviously the traditional way, the windows need to uses where the window TreeView.

In addition, if there are other events TreeView window where the need for multiple external windows or objects notice,

The need to establish a re-notification list.

Event-mode, you need to maintain your own one or more notification list, while each unit using the events require a reference unit event source.

 2.

If we use the interface mode, all events will be included into by the window where the TreeView to call:

Copy the code
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-}
Copy the code

Note that automatically opens a viewer interface must RTTI, {$ M +}

Then, just call the following, you can make all listeners (observer) of OnResize be called (all methods within interfaces can be called):

procedure TForm2.FormResize(Sender: TObject);
begin
  CurrentStateDispatcher.Source.OnResize(Width, Height);
end;

among them:

(1).

CurrentStateDispatcher.Source:ICurrentStateObserver
It is a virtual interface, also ICurrentStateObserver type. Within this method call interface, the viewer automatically call all the corresponding method.
We only need to call
CurrentStateDispatcher.Source.OnCurrentObjectChange(...);
CurrentStateDispatcher.Source.OnDataReceive(...);
CurrentStateDispatcher.Source.OnResize(...);
We can achieve call all the observer. 

(2).
CurrentStateDispatcher:IInterfaceObservable<ICurrentStateObserver>
IInterfaceObservable <ICurrentStateObserver> multicast listener mode (observer pattern) to achieve a ICurrentStateObserver interface of:
Copy the code
  IInterfaceObservable < T: IInterface >= interface
    procedure AddObserver(const aListener: T);
    procedure RemoveObserver(const aListener: T);
    function GetSource: T;
    property Source: T read GetSource;
  end;
Copy the code

AddObserver add listeners, RemoveObject is to remove the observer

Source is the virtual interface for the aforementioned multicast call.

. (3) in the object usage patterns in a statement:
Copy the code
  TForm2=class(TForm)
  ....
  private
     FCurrentStateDispatcher:IInterfaceObservable<ICurrentStateObserver>;
  public
    property CurrentStateDispatcher:IInterfaceObservable<ICurrentStateObserver> read FCurrentStateDispatcher;
  end;
Copy the code
3.
Let's look at a complete example of the use: 
uMainForm.pas
Copy the code
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.
Copy the code

uMainForm.dfm

Copy the code
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
Copy the code

 4.

 Here is uInterfaceObservable.pas

Copy the code
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.
Copy the code

 

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

Guess you like

Origin www.cnblogs.com/findumars/p/11579259.html