簡體   English   中英

如何使用dunit的DUnitWizard中包含的XPObserver單元來實現觀察者模式,甚至是MVC模式?

[英]How to use the XPObserver unit contained in dunit's DUnitWizard, to implement an observer pattern, or even a MVC pattern?

Delphi中有很好的Observer模式示例,這要歸功於Stackoverflow上的明智問題和答案,例如在Delphi中實現觀察者模式的最佳方法,以及有關如何在Delphi中使用接口的任何視頻/屏幕演員或其他資源嗎? 從這些stackoverflow問題中,提取以下指導材料鏈接:

  1. 喬安娜卡特的博客

  2. SourceMaking網站

  3. TDelphiHobbyist的博客

  4. itte.no網站

  5. dunit的DUnitWizard

在第二個stackoverflow問題中,mghie描述了dunit's DUnitWizard's XPObserver.pas非常有趣,其他XP*.pas仔細研究。 但是, XPObserver單元僅在兩個地方引用,在dunit\\Contrib\\DUnitWizard\\Source\\Common\\dunit\\XPObserverTests.pas ,其中測試的唯一興趣似乎是檢查引用計數,以及dunit\\Contrib\\DUnitWizard\\Source\\DelphiExperts\\DUnitProject\\XPTestedUnitUtils.pas ,其中僅使用在XPObserver單元中聲明的IXPFamily類型。

因此,我想知道使用這個XPObserver單元的最佳做法是什么。

例如:設計問題,例如:

(1)如何使用XPObserver單元實現一些做某事的觀察者模式?

(2)如何使用XPObserver實現MVC模式?

或編碼問題如:

(3) XPObserverTXPSubjects具有啟用single observer<->multiple subject關系的能力。 但是, FSubjects被聲明為私有。 也沒有吸氣劑。 我想知道這是設計的嗎? (例如,作者寫了// ...***DON'T*** refactor this method!!TXPSubject.DeleteObserver 。因此我沒有信心修改代碼因為我無法理解這個以及其他部分完全。)如果是這樣,使用TXPSubjects啟用single observer<->multiple subject關系的假設方式是什么?

非常感謝您的時間和意見!

讓我舉個例子來說明如何使用XPObserver單元。 前幾個接口來模擬數據模型:

type
  IColorChannel = interface(IXPSubject)
    function GetValue: byte;
    procedure RandomChange;
  end;

  IColorChannelObserver = interface(IXPObserver)
    ['{E1586F8F-32FB-4F77-ACCE-502AFDAF0EC0}']
    procedure Changed(const AChannel: IColorChannel);
  end;

  IColor = interface(IXPSubject)
    function GetValue: TColor;
  end;

  IColorObserver = interface(IXPObserver)
    ['{0E5D2FEC-5585-447B-B242-B9B57FC782F2}']
    procedure Changed(const AColor: IColor);
  end;

IColorChannel只包含一個byte值,它有返回值並隨機更改它的方法。 IColorChannelObserver接口的實現者也可以觀察它自己。

IColor只包含一個TColor值,它只有一個返回值的方法。 IColorObserver接口的實現者也可以觀察它自己。

一個實現IColorChannel的類,沒什么難的:

type
  TColorChannel = class(TXPSubject, IColorChannel)
    function GetValue: byte;
    procedure RandomChange;
  private
    fValue: byte;
  end;

function TColorChannel.GetValue: byte;
begin
  Result := fValue;
end;

procedure TColorChannel.RandomChange;
var
  Value, Idx: integer;
  Icco: IColorChannelObserver;
begin
  Value := Random(256);
  if fValue <> Value then begin
    fValue := Value;
    for Idx := 0 to ObserverCount - 1 do begin
      // Or use the Supports() function instead of QueryInterface()
      if GetObserver(Idx).QueryInterface(IColorChannelObserver, Icco) = S_OK then
        Icco.Changed(Self);
    end;
  end;
end;

現在是一個實現IColor for RGB的類,它將包含並觀察TColorChannel三個實例 - 即單個觀察者多個主題關系:

type
  TRGBColor = class(TXPSubject, IColor, IColorChannelObserver)
    function GetValue: TColor;
  private
    fRed: IColorChannel;
    fGreen: IColorChannel;
    fBlue: IColorChannel;
    fValue: TColor;
    function InternalUpdate: boolean;
  public
    constructor Create(ARed, AGreen, ABlue: IColorChannel);

    procedure Changed(const AChannel: IColorChannel);
  end;

constructor TRGBColor.Create(ARed, AGreen, ABlue: IColorChannel);
begin
  Assert(ARed <> nil);
  Assert(AGreen <> nil);
  Assert(ABlue <> nil);
  inherited Create;
  fRed := ARed;
  fRed.AddObserver(Self, fRed);
  fGreen := AGreen;
  fGreen.AddObserver(Self, fGreen);
  fBlue := ABlue;
  fBlue.AddObserver(Self, fBlue);
  InternalUpdate;
end;

procedure TRGBColor.Changed(const AChannel: IColorChannel);
var
  Idx: integer;
  Ico: IColorObserver;
begin
  if InternalUpdate then
    for Idx := 0 to ObserverCount - 1 do begin
      if GetObserver(Idx).QueryInterface(IColorObserver, Ico) = S_OK then
        Ico.Changed(Self);
    end;
end;

function TRGBColor.GetValue: TColor;
begin
  Result := fValue;
end;

function TRGBColor.InternalUpdate: boolean;
var
  Value: TColor;
begin
  Result := False;
  Value := RGB(fRed.GetValue, fGreen.GetValue, fBlue.GetValue);
  if fValue <> Value then begin
    fValue := Value;
    Result := True;
  end;
end;

如果三個通道值中的任何一個更改,顏色將應用更改,然后通知其所有觀察者。

現在使用這些類的數據模塊:

type
  TDataModule1 = class(TDataModule)
    procedure DataModuleCreate(Sender: TObject);
  private
    fRed: IColorChannel;
    fGreen: IColorChannel;
    fBlue: IColorChannel;
    fColor: IColor;
  public
    property BlueChannel: IColorChannel read fBlue;
    property GreenChannel: IColorChannel read fGreen;
    property RedChannel: IColorChannel read fRed;
    property Color: IColor read fColor;
  end;

procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
  Randomize;

  fRed := TColorChannel.Create;
  fGreen := TColorChannel.Create;
  fBlue := TColorChannel.Create;

  fColor := TRGBColor.Create(fRed, fGreen, fBlue);
end;

最后是一個使用該數據模塊並且只知道接口的表單,而不是實現類:

type
  TForm1 = class(TForm, IXPObserver, IColorChannelObserver, IColorObserver)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ButtonClick(Sender: TObject);
  public
    procedure Changed(const AChannel: IColorChannel); overload;
    procedure Changed(const AColor: IColor); overload;
    procedure ReleaseSubject(const Subject: IXPSubject;
      const Context: pointer);
  private
    fChannels: array[0..2] of IColorChannel;
    fColor: IColor;
  end;

procedure TForm1.FormCreate(Sender: TObject);
var
  Idx: integer;
begin
  Button1.Caption := 'red';
  Button1.Tag := 0;
  fChannels[0] := DataModule1.RedChannel;

  Button2.Caption := 'green';
  Button2.Tag := 1;
  fChannels[1] := DataModule1.GreenChannel;

  Button3.Caption := 'blue';
  Button3.Tag := 2;
  fChannels[2] := DataModule1.BlueChannel;

  for Idx := 0 to 2 do
    fChannels[Idx].AddObserver(Self, fChannels[Idx]);

  fColor := DataModule1.Color;
  fColor.AddObserver(Self, fColor);
end;

procedure TForm1.FormDestroy(Sender: TObject);
var
  Idx: integer;
begin
  for Idx := Low(fChannels) to High(fChannels) do
    fChannels[Idx].DeleteObserver(Self);
  fColor.DeleteObserver(Self);
end;

procedure TForm1.ButtonClick(Sender: TObject);
var
  Button: TButton;
begin
  Button := Sender as TButton;
  if (Button.Tag >= Low(fChannels)) and (Button.Tag <= High(fChannels)) then
    fChannels[Button.Tag].RandomChange;
end;

procedure TForm1.Changed(const AChannel: IColorChannel);
var
  Idx: integer;
begin
  Assert(AChannel <> nil);
  for Idx := Low(fChannels) to High(fChannels) do
    if fChannels[Idx] = AChannel then begin
      while StatusBar1.Panels.Count <= Idx do
        StatusBar1.Panels.Add;
      StatusBar1.Panels[Idx].Text := IntToStr(AChannel.GetValue);
      break;
    end;
end;

procedure TForm1.Changed(const AColor: IColor);
begin
  Assert(AColor <> nil);
  Color := AColor.GetValue;
end;

procedure TForm1.ReleaseSubject(const Subject: IXPSubject;
  const Context: pointer);
var
  Idx: integer;
begin
  // necessary if the objects implementing IXPSubject are not reference-counted
  for Idx := Low(fChannels) to High(fChannels) do begin
    if Subject = fChannels[Idx] then
      fChannels[Idx] := nil;
  end;
  if Subject = fColor then
    fColor := nil;
end;

表單實現接口但不是引用計數。 它會自行注冊以觀察數據模塊的四個屬性中的每一個,每當顏色通道發生變化時,它會在狀態欄窗格中顯示該值,當顏色更改時,它會更新自己的背景顏色。 有隨機更改顏色通道的按鈕。

數據模塊屬性和其他更改數據的方法都可能有更多的觀察者。

使用FastMM4在Delphi 5和Delphi 2009中進行測試,沒有內存泄漏。 如果表單中的每個AddObserver()沒有匹配的DeleteObserver()調用,則會出現泄漏。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM