《GOF设计模式》—观察者(OBSERVER)—Delphi源码示例:封装复杂的更新语义

来源:互联网 发布:编程四大魔道天才 编辑:程序博客网 时间:2024/04/28 17:28

示例:封装复杂的更新语义
说明:
当目标和观察者间的依赖关系特别复杂时,可能需要一个维护这些关系的对象。我们称这样的对象为更改管理器(ChangeManager)。它的目的是尽量减少观察者反映其目标的状态变化所需的工作量。例如,如果一个操作涉及到对几个相互依赖的目标进行改动,就必须保证仅在所有的目标都已更改完毕后,才一次性地通知它们的观察者,而不是每个目标都通知观察者。
clip_image002
ChangeManager是一个中介者模式(Mediator)的实例。通常只有一个ChangeManager,并且它是全局可见的。这里单件模式(Singleton)可能有用。

代码:
 clip_image002[5]
unit uObserver5;

interface

uses
    SysUtils, Classes,Dialogs;

type
    TSubject = class;
    TChangeManager = class;

    TObserver = class
    public
        destructor Destroy; override;
        //---
        procedure Update(ASubject: TSubject); virtual; abstract;
    end;
    TConcreteObserver = class(TObserver)
    private
        FState: Integer;
    public
        procedure Update(ASubject: TSubject); override;
    end;

    TSubject = class
    private
        FChman: TChangeManager;
        FNotifyEnabled: Boolean;
        procedure SetNotifyEnabled(const Value: Boolean);
    public
        constructor Create;
        destructor Destroy; override;
        //---
        procedure Attach(AObserver: TObserver);
        procedure Detach(AObserver: TObserver);
        procedure Notify();
        //---
        property NotifyEnabled: Boolean read FNotifyEnabled write SetNotifyEnabled;
    end;
    TConcreteSubject = class(TSubject)
    private
        FState: Integer;
        procedure SetState(const Value: Integer);
    public
        property State: Integer read FState write SetState;
    end;
    
    RMapInfo = record
        case Integer of
            0: (
                Item: Pointer;
                Items: TList;
                );
            1: (
                Subject: TSubject;
                Observers: TList;
                );
            2: (
                Observer: TObserver;
                Subjects: TList;
                );
    end;
    PMapInfo = ^RMapInfo;

    TMapList = class(TList)
    private
        function GetItems(Index: Integer): PMapInfo;
    protected
        procedure Notify(Ptr: Pointer; Action: TListNotification); override;
    public
        procedure Add(AItem1, AItem2: Pointer);
        function IndexOf(AItem: Pointer): Integer;
        procedure Delete(AItem1, AItem2: Pointer);
        //---
        property Items[Index: Integer]: PMapInfo read GetItems;default;
    end;

    TChangeManager = class
    private
        FSubjects: TMapList;
        FObservers: TMapList;
    public
        constructor Create;
        destructor Destroy; override;
        //---
        class function Instance: TChangeManager;
        //---
        procedure Register1(ASubject: TSubject; AObserver: TObserver);
        procedure UnRegister1(ASubject: TSubject; AObserver: TObserver);
        procedure Notify; virtual; abstract;
    end;
    TSimpleChangeManager = class(TChangeManager)
    public
        procedure Notify; override;
    end;
    TDAGChangeManager = class(TChangeManager)
    public
        procedure Notify; override;
    end;

procedure Test;

implementation

var
    FChangeManager: TChangeManager;

procedure Test;
    //---
    procedure _Test1;
    var
        ASubject: TConcreteSubject;
        AObserver: TObserver;
    begin
        ASubject := TConcreteSubject.Create;
        AObserver := TConcreteObserver.Create;
        try
            ASubject.Attach(AObserver);
            ASubject.State := 123;
        finally
            AObserver.Free;
            ASubject.Free;
        end;
    end;
    //---
    procedure _Test2;
    var
        ASubject: TConcreteSubject;
        AObserver1,AObserver2: TObserver;
    begin
        ASubject := TConcreteSubject.Create;
        AObserver1 := TConcreteObserver.Create;
        AObserver2 := TConcreteObserver.Create;
        try
            ASubject.Attach(AObserver1);
            ASubject.Attach(AObserver2);
            //---
            ASubject.State := 123;
        finally
            AObserver1.Free;
            AObserver2.Free;
            ASubject.Free;
        end;
    end;
    //---
    procedure _Test3;
    var
        ASubject1,ASubject2: TConcreteSubject;
        AObserver: TObserver;
    begin
        ASubject1 := TConcreteSubject.Create;
        ASubject2 := TConcreteSubject.Create;
        AObserver := TConcreteObserver.Create;
        try
            ASubject1.Attach(AObserver);
            ASubject2.Attach(AObserver);
            //---
            ASubject1.State := 1;
            ASubject2.State := 2;
        finally
            AObserver.Free;
            ASubject1.Free;
            ASubject2.Free;
        end;
    end;
    //---
    procedure _Test4;
    var
        ASubject1,ASubject2: TConcreteSubject;
        AObserver1,AObserver2: TObserver;
    begin
        ASubject1 := TConcreteSubject.Create;
        ASubject2 := TConcreteSubject.Create;
        AObserver1 := TConcreteObserver.Create;
        AObserver2 := TConcreteObserver.Create;
        try
            ASubject1.Attach(AObserver1);
            ASubject1.Attach(AObserver2);
            ASubject2.Attach(AObserver1);
            ASubject2.Attach(AObserver2);
            //---
            ASubject1.State := 1;
            ASubject2.State := 2;
        finally
            AObserver1.Free;
            AObserver2.Free;
            ASubject1.Free;
            ASubject2.Free;
        end;
    end;
begin
    _Test4;
end;

constructor TSubject.Create;
begin
    inherited;
    //---
    FChman := TChangeManager.Instance;
end;

destructor TSubject.Destroy;
begin
    FChman.UnRegister1(self, nil);
    //---
    inherited;
end;

procedure TSubject.Attach(AObserver: TObserver);
begin
    FChman.Register1(Self, AObserver);
end;

procedure TSubject.Detach(AObserver: TObserver);
begin
    FChman.Unregister1(Self, AObserver);
end;

procedure TSubject.Notify();
begin
    FChman.Notify;
end;

procedure TSubject.SetNotifyEnabled(const Value: Boolean);
begin
    FNotifyEnabled := Value;
    if FNotifyEnabled then
    begin
        Self.Notify;
        FNotifyEnabled := False;
    end;
end;

procedure TConcreteObserver.Update(ASubject: TSubject);
begin
    FState := FState + TConcreteSubject(ASubject).State;
    ShowMessage(IntToStr(FState));
end;

constructor TChangeManager.Create;
begin
    if FChangeManager = nil then
    begin
        FChangeManager := Self;
        FSubjects := TMapList.Create;
        FObservers := TMapList.Create;
    end
    else
        raise Exception.Create('Error');
end;

destructor TChangeManager.Destroy;
begin
    FSubjects.Free;
    FObservers.Free;
    FChangeManager := nil;
    //---
    inherited;
end;

class function TChangeManager.Instance: TChangeManager;
begin
    if FChangeManager = nil then
        FChangeManager := TSimpleChangeManager.Create;
        //FChangeManager := TDAGChangeManager.Create;
    //---
    Result := FChangeManager;
end;

procedure TChangeManager.Register1(ASubject: TSubject;
    AObserver: TObserver);
begin
    FSubjects.Add(ASubject, AObserver);
    FObservers.Add(AObserver, ASubject);
end;

procedure TChangeManager.UnRegister1(ASubject: TSubject;
    AObserver: TObserver);
begin
    FSubjects.Delete(ASubject, AObserver);
    FObservers.Delete(AObserver, ASubject);
end;

procedure TSimpleChangeManager.Notify;
var
    i, j: Integer;
    ASubject: TSubject;
    AObserver: TObserver;
begin
    for i := 0 to FSubjects.Count - 1 do
    begin
        with FSubjects[i]^ do
        begin
            ASubject := Subject;
            if ASubject.NotifyEnabled then
            begin
                for j := 0 to Observers.Count - 1 do
                begin
                    AObserver := Observers[j];
                    AObserver.Update(ASubject);
                end;
            end;
        end;
    end;
end;

procedure TMapList.Add(AItem1, AItem2: Pointer);
    //---
    procedure _Add; overload;
    var
        pData: PMapInfo;
    begin
        New(pData);
        pData.Items := TList.Create;
        //---
        pData.Item := AItem1;
        pData.Items.Add(AItem2);
        //---
        inherited Add(pData);
    end;
    //---
    procedure _Add(AList: TList); overload;
    var
        AIndex: Integer;
    begin
        AIndex := AList.IndexOf(AItem2);
        if AIndex < 0 then
            AList.Add(AItem2);
    end;
var
    AIndex: Integer;
begin
    AIndex := Self.IndexOf(AItem1);
    if AIndex < 0 then
        _Add
    else
        _Add(Self.Items[AIndex].Items);
end;

procedure TMapList.Delete(AItem1, AItem2: Pointer);
    //---
    procedure _DelItem(AList: TList);
    var
        AIndex: Integer;
    begin
        with AList do
        begin
            AIndex := IndexOf(AItem2);
            if AIndex >= 0 then
                Delete(AIndex);
        end;
    end;
    //---
    procedure _Delete1;
    var
        i: Integer;
    begin
        for i := self.Count - 1 downto 0 do
        begin
            with Self.Items[i]^ do
            begin
                _DelItem(Items);
                if Items.Count = 0 then
                    inherited Delete(i);
            end;
        end;
    end;
    //---
    procedure _Delete2;
    var
        AIndex: Integer;
    begin
        AIndex := self.IndexOf(AItem1);
        if AIndex >= 0 then
            inherited Delete(AIndex);
    end;
    //---
    procedure _Delete3;
    var
        AIndex: Integer;
    begin
        AIndex := self.IndexOf(AItem1);
        if AIndex >= 0 then
        begin
            with Self.Items[AIndex]^ do
            begin
                _DelItem(Items);
                if Items.Count = 0 then
                    inherited Delete(AIndex);
            end;
        end;
    end;
begin
    if (AItem1 = nil) and (AItem2 = nil) then
        Exit;
    //---
    if (AItem1 = nil) then
        _Delete1
    else if (AItem2 = nil) then
        _Delete2
    else
        _Delete3;
end;

function TMapList.GetItems(Index: Integer): PMapInfo;
begin
    Result := inherited Items[Index];
end;

function TMapList.IndexOf(AItem: Pointer): Integer;
var
    i: Integer;
begin
    for i := 0 to self.Count - 1 do
    begin
        if (Self.Items[i].Item = AItem) then
        begin
            Result := i;
            Exit;
        end;
    end;
    //---
    Result := -1;
end;

procedure TMapList.Notify(Ptr: Pointer; Action: TListNotification);
var
    pData: PMapInfo;
begin
    if Action = lnDeleted then
    begin
        pData := Ptr;
        pData.Items.Free;
        Dispose(pData);
    end;
    //---
    inherited;
end;

destructor TObserver.Destroy;
begin
    TChangeManager.Instance.UnRegister1(nil, self);
    //---
    inherited;
end;

procedure TDAGChangeManager.Notify;
var
    i: Integer;
    ASubject: TSubject;
    AObserver: TObserver;
begin
    for i := 0 to FObservers.Count - 1 do
    begin
        with FObservers[i]^ do
        begin
            AObserver := Observer;
            if Subjects.Count > 0 then
            begin
                ASubject := Subjects.Last;
                if ASubject.NotifyEnabled then
                    AObserver.Update(ASubject);
            end;
        end;
    end;
end;

procedure TConcreteSubject.SetState(const Value: Integer);
begin
    FState := Value;
    Self.NotifyEnabled := True;
end;

initialization
    FChangeManager := nil;

finalization
    if FChangeManager <> nil then
        FChangeManager.Free;

end.