2012-06-19 16 views
6

Istnieją dobre przykłady wzoru obserwatora w Delphi, dzięki mądrym pytaniom & odpowiedzi na Stackoverflow, takich jak Best way to implement observer pattern in Delphi i Are there any Videos/Screen casts or other resources on how to use Interfaces in Delphi?. Z tych StackOverflow pytania, poniższe linki pouczających materiałów są wydobywane:Jak korzystać z jednostki XPObserver zawartej w DUititWizard dunit, aby zaimplementować wzorzec obserwatora lub nawet wzorzec MVC?

  1. Joanna Carter's blog

  2. SourceMaking site

  3. TDelphiHobbyist's blog

  4. itte.no site

  5. dunit's DUnitWizard

W tej drugiej kwestii stackoverflow, mghie opisane dunit's DUnitWizard's XPObserver.pas jako bardzo ciekawe i inne XP*.pas jak worthing się bliżej. Jednak jednostka XPObserver odwołuje się tylko w dwóch miejscach, w dunit\Contrib\DUnitWizard\Source\Common\dunit\XPObserverTests.pas, gdzie jedynym zainteresowaniem testu wydaje się być sprawdzenie zliczania odwołań i dunit\Contrib\DUnitWizard\Source\DelphiExperts\DUnitProject\XPTestedUnitUtils.pas, gdy używany jest tylko typ IXPFamily zadeklarowany w jednostce XPObserver.

Zastanawiam się więc, jaka jest najlepsza praktyka używania tego urządzenia XPObserver.

Na przykład: kwestie konstrukcyjne, takie jak:

(1) W jaki sposób korzystać z urządzenia XPObserver zaimplementować wzorzec obserwatora, że ​​coś zrobić?

(2) Jak korzystać z XPObserver do wdrożenia wzoru MVC?

Albo kodowanie pytania:

(3) XPObserver „s TXPSubjects zastrzeżono w celu zapewnienia zdolności umożliwiające single observer<->multiple subject relację. Jednak FSubjects jest deklarowany jako prywatny. Nie ma również żadnych pobierających. Zastanawiam się, czy jest to zgodne z projektem? (Na przykład autor napisał // ...***DON'T*** refactor this method!! w TXPSubject.DeleteObserver. W związku z tym nie jestem pewien, aby zmodyfikować kod, ponieważ nie mogę zrozumieć, to i być może inne części całkowicie.) Jeśli tak, jaki jest rzekomy sposób korzystania z TXPSubjects, aby umożliwić relację single observer<->multiple subject?

Dziękuję bardzo za poświęcony czas i uwagi!

Odpowiedz

1

Podam przykład użycia serwera XPObserver. Pierwsze kilka interfejsów do symulacji modelu danych:

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 tylko owija wartość byte, ma metody, aby zwrócić wartość oraz losowo zmienić. Jest to również możliwe do zaobserwowania przez implementatorów interfejsu IColorChannelObserver, które rejestrują się razem z nim.

IColor po prostu zawija wartość TColor, ma tylko metodę zwracania wartości. Jest to również obserwowalne przez implementatorów interfejsu IColorObserver, które rejestrują się razem z nim.

Klasa realizacji IColorChannel, nic nie trudno o nim:

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; 

Teraz klasa wykonawczych IColor dla RGB, które będą zawierać i obserwować trzy instancje z TColorChannel - czyli relacja pojedynczy obserwator wielu przedmiotów:

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; 

Jeśli którakolwiek z wartości trzech kanałów ulegnie zmianie, kolor zastosuje zmianę, a następnie powiadomi wszystkich swoich obserwatorów.

Teraz moduł danych przy użyciu tych klas:

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; 

a na końcu formularza, który używa tego modułu danych i wie tylko o interfejsach, nic o klasach wykonawczych:

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; 

Formularz implementuje interfejsy, ale nie jest zliczany z odniesieniami. Rejestruje się, aby obserwować każdą z czterech właściwości modułu danych, gdy zmienia się kanał koloru, pokazuje wartość w okienku paska stanu, kiedy zmienia się kolor, zmienia swój własny kolor tła. Są przyciski do losowej zmiany kanałów kolorów.

Może być więcej obserwatorów dla właściwości modułu danych i innych sposobów zmiany danych.

Testowany w Delphi 5 i Delphi 2009 za pomocą FastMM4, nie ma wycieków pamięci. Wystąpią przecieki, gdy w formularzu nie ma odpowiadającego połączenia DeleteObserver() dla każdego AddObserver().