2012-06-19 2 views
6

Best way to implement observer pattern in DelphiAre there any Videos/Screen casts or other resources on how to use Interfaces in Delphi?のような、Stackoverflowに関する賢明な質問&のおかげで、DelphiのObserverパターンの良い例があります。これらのstackoverflowの質問からは、有益な材料の以下のリンクが抽出されていますdunitのDUnitWizardに含まれるXPObserverユニットを使用して、オブザーバパターン、またはMVCパターンを実装する方法は?

  1. Joanna Carter's blog

  2. SourceMaking site

  3. TDelphiHobbyist's blog

  4. itte.no site

  5. その二stackoverflowの質問に

、mghieは詳しく見てワーシングと非常に興味深く、XP*.pasdunit's DUnitWizard's XPObserver.pasを説明しました。しかし、XPObserverユニットは、参照カウントをチェックするように見えるdunit\Contrib\DUnitWizard\Source\Common\dunit\XPObserverTests.pasと、XPObserverユニットで宣言されたIXPFamilyタイプのみが使用されるdunit\Contrib\DUnitWizard\Source\DelphiExperts\DUnitProject\XPTestedUnitUtils.pasの2つの場所でのみ参照されます。

したがって、このXPObserverユニットを使用するベストプラクティスは何か不思議です。例えば

次のようなデザインの質問、:

(1)何かをオブザーバーパターンを実装するためにXPObserverユニットを使用する方法?

(2)XPObserverを使用してMVCパターンを実装する方法は?

などの質問にコーディング:

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

あなたの時間とコメントをありがとう!

答えて

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; 

TColorChannelの3つのインスタンス含めると観察するRGB用IColorを実装さて、クラス、 - 単一の観察者の複数の被験者の関係、すなわち:

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つのプロパティのそれぞれを観察するために自身を登録します。カラーチャネルが変更されるたびに、ステータスバーペインの値を表示します。色チャンネルをランダムに変更するボタンがあります。

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

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

関連する問題