• 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.
  • 相关阅读:
    cmd启动数据库时,出现 (无法启动此程序,因为计算机中丢失VCRUNTIME140_1.dll 尝试重新安装此程序以解决此问题 )解决方法。
    浅谈Promise语法API+封装
    浅谈JS回调地狱
    MySQL数据库安装步骤
    将MongoDB安装为Windows服务---安装MongoDB服务
    后缀.msc文件是什么?
    Mongoose类库使用教程---实现增删改查
    MongoDB可视化工具--Robo 3T 安装使用教程
    久违的锻炼
    出差(2~十四)
  • 原文地址:https://www.cnblogs.com/hezihang/p/6083555.html
Copyright © 2020-2023  润新知