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