📄 xpobserver.pas
字号:
unit XPObserver;
{
$Source: /cvsroot/dunit/dunit/Contrib/DUnitWizard/Source/Common/XPObserver.pas,v $
$Revision: 1.2 $
$Date: 2004/05/03 15:07:15 $
Last amended by $Author: pvspain $
$State: Exp $
XPObserver:
Interfaces and base classes to handle the mutual reference problem, wherein
two objects prevent each other from being destroyed. Each holds a
reference to the other, consequently neither's reference counts can reduce to
zero, and their destructors will never be called.
There are two commonly observed instances of this problem. The first is
Parent/Child, where the Parent is responsible for the lifetime of the Child.
Parent/Child is a particular case of the more general second scenario,
Observer/Subject, where there is no lifetime relationship between the parties.
There are two parties in Observer/Subject, an Observer, and the party being
observed, the Subject. The Observer must implement IXPObserver, and the Subject
must implement IXPSubject. The base class TXPSubject implements both IXPSubject
and IXPObserver, as this relationship is often chained - the Observer itself
has Observers and so on. IXPObserver is easy to otherwise implement, as it
has only one method, ReleaseSubject, which has a very simple implementation.
The Observer obtains a reference to Subject and registers interest by calling
Subject.AddObserver() and passing itself (as an IXPObserver or descendant
interface) and the Subject (as an IXPSubject or descendant interface) as
arguments. Optionally, the Observer can also pass some context-specific
information as Context (defaults to nil), which will be returned unaltered
in a later ReleaseSubject() callback on the Observer.
When the Subject is being destroyed, it will call Observer.ReleaseSubject()
on all registered Observers, passing itself (the Subject reference passed in
via Subject.AddObserver()) and the Observer-supplied Context argument
(defaults to nil) as the arguments.
The Observers, in response, must release their reference to the Subject. The
Subject then deletes the Observer from its list of Observers, releasing its
reference to the Observer in the process.
If an Observer wishes to initiate detachment from the Subject, it must call
Subject.DeleteObserver(), passing itself (IXPObserver), and any Context passed
earlier to Subject.AddObserver() as the argument. The Subject will respond by
calling Observer.ReleaseSubject, wherein the Observer must release its
reference to the Subject (Subject := nil).
In the Parent/Child scenario, both parties must implement IXPFamily, to
allow for hierarchical relationships. Use the base class TXPFamily for
parents and children, passing the parent in the constructor and, typically, no
further action is required. The interface provides accessor methods for the
Parent and any Children. The Parent can also accept non-Child observers, as
it derives from IXPSubject.
Copyright (c) 2002,2003 by The Excellent Programming Company Pty Ltd
(Australia) (ABN 27 005 394 918).
Contact Paul Spain via email: paul@xpro.com.au
This unit is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This unit is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this unit; if not, the license can be viewed at:
http://www.gnu.org/copyleft/lesser.html
or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
}
interface
uses
XPInterfacedObject, // TXPInterfacedObject
SyncObjs, // TCriticalSection
Classes; // IInterfaceList
type
/////////////////////////////////////////////////////////////////////////////
// Interface declarations
/////////////////////////////////////////////////////////////////////////////
IXPSubject = interface;
IXPObserver = interface
['{DF1C9798-E422-42B0-8534-26CC28214DFB}']
procedure ReleaseSubject(const Subject: IXPSubject;
const Context: pointer);
end;
// IXPSubject inherits from IXPObserver to enable observer chains and
// parent-child hierarchies, and provides a simple linkage for cascading
// notifications.
IXPSubject = interface(IXPObserver)
['{D7E3FD5D-0A70-4095-AF41-433E7E4A9C29}']
function AddObserver(const Observer: IXPObserver;
const Subject: IXPSubject; const Context: pointer = nil): boolean;
function InsertObserver(const idx: integer; const Observer: IXPObserver;
const Subject: IXPSubject; const Context: pointer = nil): boolean;
function DeleteObserver(const Observer: IXPObserver;
const Context: pointer = nil): boolean;
procedure DeleteObservers;
function ObserverCount: integer;
function GetObserver(const idx: integer): IXPObserver;
property Observers[const idx: integer]: IXPObserver read GetObserver;
property Count: integer read ObserverCount;
end;
IXPFamily = interface(IXPSubject)
['{D624A50F-F6D3-426C-BB74-2503FE654546}']
function GetParent: IXPFamily;
procedure SetParent(const AParent: IXPFamily);
property Parent: IXPFamily read GetParent write SetParent;
property Children[const idx: integer]: IXPObserver read GetObserver;
end;
PInterface = ^IInterface;
TXPReleaser = procedure(const LocalRef: PInterface) of object;
// IXPSubjects:
// Convenience interface to automate the management of subject/observer
// relationships. Typically, an observer class (which contains references to
// subjects) would contain a member instance of type IXPSubjects to
// manage its subjects.
IXPSubjects = interface(IXPObserver)
['{BA5B61FE-DE3D-47A4-83D6-5AD555E391D5}']
// Add an interface to be managed, and an optional callback method
// <Releaser> to be called when that interface must be released
function AddSubject(const LocalRef: PInterface;
const Releaser: TXPReleaser = nil): boolean;
// Observer-initiated detachment from subject.
function DeleteSubject(const LocalRef: PInterface): boolean;
// Observer-initiated detachment from all subjects. Should be called
// from container class's destructor.
procedure Clear;
end;
/////////////////////////////////////////////////////////////////////////////
// Interface implementor declarations
/////////////////////////////////////////////////////////////////////////////
PXPSubjectInfo = ^TXPSubjectInfo;
TXPSubjectInfo = record
AsPassed: PInterface;
AsSubject: pointer;
Releaser: TXPReleaser;
end;
TXPSubjects = class(TXPInterfacedObject, IXPObserver, IXPSubjects)
private
FSubjects: TList;
// Initialised to false by default
FDestroying: boolean;
function FindInterface(const LocalRef: PInterface;
out idx: integer): boolean;
protected
// IXPObserver implementation
procedure ReleaseSubject(const ASubject: IXPSubject;
const Context: pointer); virtual;
// IXPSubjects implementation
// We allow duplicate values, but not duplicate references, so pass by
// reference to ensure there are no duplicate references. Said another way,
// we can have more than one local reference (interface variable) to a
// subject, but each local reference can only be registered once.
function AddSubject(const LocalRef: PInterface;
const Releaser: TXPReleaser = nil): boolean;
// Observer initiated detachment from Subject. Pass by reference to
// ensure we get the right reference
function DeleteSubject(const LocalRef: PInterface): boolean;
procedure Clear;
public
constructor Create(const ADelegator: IInterface = nil);
destructor Destroy; override;
end;
TXPSubject = class(TXPSubjects, IXPSubject, IInterface)
private
FDeletingObservers: boolean;
function FindObserver(const Observer: IXPObserver;
const Context: pointer; out idx: integer): boolean;
protected
FSync: TCriticalSection;
FObservers: TList;
function SameContent(
const ObserverA, ObserverB: IXPObserver): boolean; virtual;
// IInterface partial reimplementation
function _Release: Integer; stdcall;
// IXPSubject implementation
function AddObserver(const Observer: IXPObserver;
const Subject: IXPSubject; const Context: pointer = nil): boolean;
function InsertObserver(const idx: integer; const Observer: IXPObserver;
const Subject: IXPSubject; const Context: pointer = nil): boolean;
function DeleteObserver(const Observer: IXPObserver;
const Context: pointer = nil): boolean;
procedure DeleteObservers;
function ObserverCount: integer;
function GetObserver(const idx: integer): IXPObserver;
public
constructor Create(const ADelegator: IInterface = nil);
destructor Destroy; override;
end;
TXPFamily = class(TXPSubject, IXPFamily)
protected
FParent: IXPFamily;
// Reimplement method to cascade request to children.
procedure ReleaseSubject(const Subject: IXPSubject;
const Context: pointer); override;
function GetParent: IXPFamily; virtual;
procedure SetParent(const AParent: IXPFamily); virtual;
public
constructor Create(const AParent: IXPFamily = nil;
const ADelegator: IInterface = nil);
end;
implementation
uses
SysUtils;
const CVSID: string = '$Header: /cvsroot/dunit/dunit/Contrib/DUnitWizard/Source/Common/XPObserver.pas,v 1.2 2004/05/03 15:07:15 pvspain Exp $';
/////////////////////////////////////////////////////////////////////////////
// TXPSubjects implementation
/////////////////////////////////////////////////////////////////////////////
constructor TXPSubjects.Create(const ADelegator: IInterface);
begin
inherited Create(ADelegator);
FSubjects := TList.Create;
end;
destructor TXPSubjects.Destroy;
begin
// Set object-scope state so we can distinguish from user-initiated
// detachment from subjects.
FDestroying := true;
Clear;
FSubjects.Free;
inherited Destroy;
end;
procedure TXPSubjects.Clear;
var
idx: integer;
SubjectInfo: PXPSubjectInfo;
begin
// Disconnect from all subjects
for idx := FSubjects.Count - 1 downto 0 do
begin
SubjectInfo := PXPSubjectInfo(FSubjects[idx]);
// This call will consequentially invoke self.ReleaseSubject which will
// delete the FSubjects item referenced by SubjectInfo. Hence, next
// iteration we will be dealing with the new last item in FSubjects.
// We could just deal with FSubjects[FSubjects.Count - 1] and avoid the
// loop counter, idx, but there is potential for an infinite loop if the
// subject doesn't call back into self.ReleaseSubject
IXPSubject(SubjectInfo^.AsSubject).DeleteObserver(self,
SubjectInfo^.AsPassed);
end;
// Taking a pessimistic view, clean up any subjects who haven't responded
while FSubjects.Count > 0 do
begin
// Drop local reference to subject - don't call Releaser at this point
// as we're probably in a minefield of dangling references
PXPSubjectInfo(FSubjects[0])^.AsPassed^ := nil;
// Finalise FSubjects entry
System.Dispose(FSubjects[0]);
FSubjects.Delete(0);
// FSubjects.Count decrements and any remainder move up one slot in list
end;
end;
function TXPSubjects.AddSubject(const LocalRef: PInterface;
const Releaser: TXPReleaser): boolean;
var
ASubject: IXPSubject;
SubjectInfo: PXPSubjectInfo;
idx: integer;
begin
// We allow duplicate values but not duplicate references, so check for
// matching reference
Result := System.Assigned(LocalRef)
and SysUtils.Supports(LocalRef^, IXPSubject, ASubject)
and not FindInterface(LocalRef, idx);
if Result then
begin
// Setup new item for FSubjects
System.New(SubjectInfo);
// Store ASubject as pointer to leave ref count unaffected
SubjectInfo^.AsPassed := LocalRef;
SubjectInfo^.AsSubject := pointer(ASubject);
SubjectInfo^.Releaser := Releaser;
// Add new item
FSubjects.Add(SubjectInfo);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -