6

Stackoverflowに関する賢明な質問と回答のおかげで、Delphiのオブザーバーパターンの良い例があります。たとえば 、Delphiでオブザーバーパターンを実装するための最良の方法、Delphiでインターフェイスを使用する方法に関するビデオ/スクリーンキャストやその他のリソースはありますか?。これらのスタックオーバーフローの質問から、有益な資料の次のリンクが抽出されます。

  1. JoannaCarterのブログ

  2. SourceMakingサイト

  3. TDelphiHobbyistのブログ

  4. itte.noサイト

  5. dunitのDUnitWizard

その2番目のstackoverflowの質問では、mghieはdunit's DUnitWizard's XPObserver.pas非常に興味深く、他の質問は詳しく調べる価値があると説明しましXP*.pasた。ただし、XPObserverユニットは2つの場所でのみ参照されますdunit\Contrib\DUnitWizard\Source\Common\dunit\XPObserverTests.pas。ここでは、テストの唯一の関心は参照カウントのチェックであるように見えdunit\Contrib\DUnitWizard\Source\DelphiExperts\DUnitProject\XPTestedUnitUtils.pas、XPObserverユニットで宣言されたIXPFamilyタイプのみが使用されます。

したがって、このXPObserverユニットを使用する際のベストプラクティスは何でしょうか。

例:次のような設計の質問:

(1)XPObserverユニットを使用して何かを行うオブザーバーパターンを実装するにはどうすればよいですか?

(2)XPObserverMVCパターンを実装するためにどのように使用しますか?

または、次のようなコーディングの質問:

(3)は、関係を可能にする機能を提供XPObserverするTXPSubjectsと主張されています。single observer<->multiple subjectただし、FSubjectsプライベートとして宣言されます。ゲッターもありません。これは仕様によるものなのかしら?(たとえば、作者は書い// ...***DON'T*** refactor this method!!ていTXPSubject.DeleteObserverます。したがって、これや他の部分を完全に理解できないため、コードを変更する自信がありません。)もしそうなら、TXPSubjectsを使用してsingle observer<->multiple subject関係を有効にするための想定される方法は何ですか?

お手数をおかけしますが、よろしくお願いいたします。

4

1 に答える 1

1

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これで、 RGB を実装するクラスが作成されました。このクラスには、3 つのインスタンスが含まれ、観察されます。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;

3 つのチャネル値のいずれかが変更されると、色が変更を適用し、すべてのオブザーバーに通知します。

これらのクラスを使用するデータ モジュール:

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;

フォームはインターフェイスを実装していますが、参照カウントされていません。それ自体を登録して、データ モジュールの 4 つのプロパティのそれぞれを監視します。カラー チャネルが変更されるとステータス バー ペインに値が表示され、色が変更されると独自の背景色が更新されます。カラーチャンネルをランダムに変更するボタンがあります。

データ モジュールのプロパティと、データを変更する他の手段の両方に、より多くのオブザーバーが存在する可能性があります。

FastMM4 を使用して Delphi 5 と Delphi 2009 の両方でテストされ、メモリ リークはありません。DeleteObserver()フォームにfor eachの一致する呼び出しがない場合、リークが発生しAddObserver()ます。

于 2012-06-22T10:31:18.823 に答える