2012-06-19 19 views
6

Có các ví dụ điển hình về mẫu Observer ở Delphi, nhờ các câu hỏi khôn ngoan & câu trả lời trên Stackoverflow, chẳng hạn như Best way to implement observer pattern in DelphiAre there any Videos/Screen casts or other resources on how to use Interfaces in Delphi?. Từ những câu hỏi stackoverflow, các liên kết sau đây của vật liệu bài học được rút ra:Làm thế nào để sử dụng đơn vị XPObserver chứa trong dnit của DUnitWizard, để thực hiện một mô hình quan sát, hoặc thậm chí một mẫu MVC?

  1. Joanna Carter's blog

  2. SourceMaking site

  3. TDelphiHobbyist's blog

  4. itte.no site

  5. dunit's DUnitWizard

Trong câu hỏi stackoverflow thứ hai, mghie mô tả dunit's DUnitWizard's XPObserver.pas như rất thú vị và XP*.pas khác như Worthing xem xét kỹ hơn. Tuy nhiên, đơn vị XPObserver chỉ được tham chiếu ở hai vị trí, trong dunit\Contrib\DUnitWizard\Source\Common\dunit\XPObserverTests.pas nơi lợi ích duy nhất của thử nghiệm dường như đang kiểm tra số đếm tham chiếu và dunit\Contrib\DUnitWizard\Source\DelphiExperts\DUnitProject\XPTestedUnitUtils.pas chỉ sử dụng loại IXPFamily trong đơn vị XPObserver.

Do đó, tôi tự hỏi thực tiễn tốt nhất khi sử dụng đơn vị XPObserver này là gì.

Ví dụ: câu hỏi thiết kế, chẳng hạn như:

(1) Làm thế nào để sử dụng các đơn vị XPObserver để thực hiện một mô hình quan sát rằng làm điều gì đó?

(2) Cách sử dụng XPObserver để triển khai mẫu MVC?

Hoặc mã hóa câu hỏi như:

(3) XPObserver 's TXPSubjects là yêu cầu để cung cấp khả năng tạo điều kiện cho mối quan hệ single observer<->multiple subject. Tuy nhiên, FSubjects được khai báo là riêng tư. Cũng không có getters. Tôi tự hỏi điều này là do thiết kế? (Ví dụ, tác giả đã viết // ...***DON'T*** refactor this method!! trong TXPSubject.DeleteObserver. Tôi không tự tin sửa đổi mã vì tôi không thể hiểu điều này và có thể là các phần khác hoàn toàn.) Nếu vậy, cách được cho là sử dụng TXPSubjects để cho phép mối quan hệ single observer<->multiple subject là gì?

Cảm ơn bạn rất nhiều vì đã dành thời gian và nhận xét!

Trả lời

1

Để tôi cung cấp cho bạn một ví dụ về cách sử dụng đơn vị XPObserver. Đầu tiên nhiều giao diện để mô phỏng một mô hình dữ liệu:

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 chỉ kết thúc tốt đẹp một giá trị byte, nó có phương pháp để trả về giá trị và thay đổi một cách ngẫu nhiên nó. Nó cũng được quan sát bởi những người triển khai giao diện IColorChannelObserver tự đăng ký với nó.

IColor chỉ cần kết thúc giá trị TColor, nó chỉ có phương thức trả về giá trị. Nó cũng có thể được quan sát bởi những người triển khai giao diện IColorObserver tự đăng ký với giao diện đó.

Một lớp thực hiện IColorChannel, không có gì khó khăn về nó:

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; 

Bây giờ một lớp học thực hiện IColor cho RGB, mà sẽ chứa và quan sát ba trường hợp của TColorChannel - tức là đơn quan sát nhiều đối tượng liên quan:

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; 

Nếu một trong ba giá trị kênh thay đổi, màu sắc sẽ áp dụng thay đổi và lần lượt thông báo cho tất cả các quan sát viên của nó.

Bây giờ một module dữ liệu sử dụng các lớp:

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; 

Và cuối cùng là một hình thức sử dụng mà module dữ liệu và chỉ biết về các giao diện, không có gì về các lớp thực hiện:

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; 

Dạng triển khai giao diện nhưng không được tính tham chiếu. Nó đăng ký chính nó để quan sát từng thuộc tính của mô-đun dữ liệu, bất cứ khi nào thay đổi kênh màu cho thấy giá trị trong ngăn trạng thái, khi màu thay đổi nó sẽ cập nhật màu nền của chính nó. Có các nút để thay đổi ngẫu nhiên các kênh màu.

Có thể có nhiều người quan sát hơn đối với các thuộc tính mô-đun dữ liệu và các phương tiện thay đổi dữ liệu khác.

Được thử nghiệm trong cả Delphi 5 và Delphi 2009 bằng FastMM4, không có rò rỉ bộ nhớ. Sẽ có rò rỉ khi không có cuộc gọi phù hợp với số DeleteObserver() cho mỗi AddObserver() trong biểu mẫu.

Các vấn đề liên quan