程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Delphi的基於接口(IInterface)的多播監聽器模式(觀察者模式 ),iinterface監聽器

Delphi的基於接口(IInterface)的多播監聽器模式(觀察者模式 ),iinterface監聽器

編輯:Delphi

Delphi的基於接口(IInterface)的多播監聽器模式(觀察者模式 ),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.

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved