INode = interface function GetParent: INode; function ChildCount: Integer; function GetChild(Index: Integer): INode; end;
If inside a class that implements the INode interface, the parent and children would be stored as follows:
TNode = class(TInterfacedObject, INode) private FParent: INode; FChild: array of INode; end;
that tree would never be destroyed. The parent keeps references to the children (and thereby increases the counter for them), and the children to the parent. This is the classic problem of circular references, in which case weak links are resorted to. In new XE delphs, you can write this:
TNode = class(TInterfacedObject, INode) private [weak] FParent: INode; FChild: array of INode; end;
and in the old - store Pointer:
TNode = class(TInterfacedObject, INode) private FParent: Pointer; FChild: array of INode; end;
This allows you to bypass the auto-increment of the counters, and now if we lose the pointer to the parent, the entire tree will be stuck, as required.
IWeakRef = interface function IsAlive: Boolean; function Get: IUnknown; end; TWeakRef = class(TInterfacedObject, IWeakRef) private FOwner: Pointer; public procedure _Clean; function IsAlive: Boolean; function Get: IUnknown; end; procedure TWeakRef._Clean; begin FOwner := nil; end; function TWeakRef.Get: IUnknown; begin Result := IUnknown(FOwner); end; function TWeakRef.IsAlive: Boolean; begin Result := Assigned(FOwner); end;
This is the usual typecast to Pointer. It is the weak link I mentioned above. But the key method, IsAlive , which returns True — if the object referenced by the weak link — still exists. It remains only to understand how beautifully to clean FOwner .
IWeakly = interface ['{F1DFE67A-B796-4B95-ADE1-8AA030A7546D}'] function WeakRef: IWeakRef; end;
which returns a weak link and write a class that implements this interface:
TWeaklyInterfacedObject = class(TInterfacedObject, IWeakly) private FWeakRef: IWeakRef; public function WeakRef: IWeakRef; destructor Destroy; override; end; destructor TWeaklyInterfacedObject.Destroy; begin inherited; FWeakRef._Clean; end; function TWeaklyInterfacedObject.WeakRef: IWeakRef; var obj: TWeakRef; begin if FWeakRef = nil then begin obj := TWeakRef.Create; obj.FOwner := Self; FWeakRef := obj; end; Result := FWeakRef; end;
We simply added a method that distributes one weak link to everyone. And since the object itself is always aware of its weak link, it simply cleans it in its destructor. It now remains only to inherit from TWeaklyInterfacedObject instead of TInterfacedObject , and that's it. No more unsafe type casts, leg shots, and foul language.
IMouseEvents = interface procedure OnMouseMove(...); procedure OnMouseDown(...); procedure OnMouseUp(...); end;
and pass it, instead of the classical procedure of object; for example, in the Subscribe / Unsubscribe method pair:
IForm = interface procedure SubscribeMouse(const subscriber: IMouseEvents); procedure UnsubscribeMouse(const subscriber: IMouseEvents); end;
When the code grows, and the interface IMouseEvents changes slightly (for example, they add a method) - it begins to strain the refactoring. For example, the same IMouseEvents is used in IForm , IButton , IImage and other evil spirits. Everywhere you need to correctly correct the subscription, add a crawl on subscribers, etc.
IPublisher = interface ['{CDE9EE5C-021F-4942-A92A-39FC74395B4B}'] procedure Subscribe (const ASubscriber: IUnknown); procedure Unsubscribe(const ASubscriber: IUnknown); end;
The class that will implement this interface (let it be TBasePublisher ) can only add and remove some interfaces from the list. In the following, we write classes that I call Broadcast. Here we have an event interface:
IGraphEvents = interface ['{2C7EF06A-2D63-4F25-80BC-7BA747463DB6}'] procedure OnAddItem(const ASender: IGraphList; const AItem: TGraphItem); procedure OnClear(const ASender: IGraphList); end;
We inherit from TBasePublisher and implement such a broadcaster:
TGraphEventsBroadcaster = class(TBasePublisher, IGraphEvents) private procedure OnAddItem(const ASender: IGraphList; const AItem: TGraphItem); procedure OnClear(const ASender: IGraphList); end; procedure TGraphEventsBroadcaster.OnAddItem(const ASender: IGraphList; const AItem: TGraphItem); var arr: TInterfacesArray; i: Integer; ev: IGraphEvents; begin arr := GetItems; for i := 0 to Length(arr) - 1 do if Supports(arr[i], IGraphEvents, ev) then ev.OnAddItem(ASender, AItem); end; procedure TGraphEventsBroadcaster.OnClear(const ASender: IGraphList); var arr: TInterfacesArray; i: Integer; ev: IGraphEvents; begin arr := GetItems; for i := 0 to Length(arr) - 1 do if Supports(arr[i], IGraphEvents, ev) then ev.OnClear(ASender); end;
that is, the broadcaster itself implements an event interface, and in implementation it simply sends out the same event to all subscribers. Advantage - everything is implemented in one place, it will not compile if you change IGraphEvents even a little. Now the IForm , IButton , IImage Zoo simply creates a TGraphEventsBroadcaster inside of itself and calls its methods, as if the IForm has only one subscriber.
IPublisher = interface ['{CDE9EE5C-021F-4942-A92A-39FC74395B4B}'] procedure Subscribe (const ASubscriber: IWeakly); procedure Unsubscribe(const ASubscriber: IWeakly); end;
Internally, the publisher TBasePublisher stores an array of weak links TWeakRefArr = array of IWeakRef;
TBasePublisher = class(TInterfacedObject, IPublisher) private FItems: TWeakRefArr; protected function GetItems: TWeakRefArr; public procedure Subscribe (const ASubscriber: IWeakly); procedure Unsubscribe(const ASubscriber: IWeakly); end;
And broadcaster now only checks for a weak reference to viability, gets a normal one, and sends the event to it. Broadcast has exchanged like this:
procedure TGraphEventsBroadcaster.OnAddItem(const ASender: IGraphList; const AItem: TGraphItem); var arr: TWeakRefArr; i: Integer; ev: IGraphEvents; begin arr := GetItems; for i := 0 to Length(arr) - 1 do if IsAlive(arr[i]) then if Supports(arr[i].Get, IGraphEvents, ev) then ev.OnAddItem(ASender, AItem); end; procedure TGraphEventsBroadcaster.OnClear(const ASender: IGraphList); var arr: TWeakRefArr; i: Integer; ev: IGraphEvents; begin arr := GetItems; for i := 0 to Length(arr) - 1 do if IsAlive(arr[i]) then if Supports(arr[i].Get, IGraphEvents, ev) then ev.OnClear(ASender); end;
Now we absolutely do not care about the order of unsubscribe. If we forgot to unsubscribe - do not worry. Everything became transparent, as it should have been before.
TAutoPublisher = packed record Publisher: IPublisher; class operator Add(const APublisher: TAutoPublisher; const ASubscriber: IWeakly): Boolean; class operator Subtract(const APublisher: TAutoPublisher; const ASubscriber: IWeakly): Boolean; end; class operator TAutoPublisher.Add(const APublisher: TAutoPublisher; const ASubscriber: IWeakly): Boolean; begin APublisher.Publisher.Subscribe(ASubscriber); Result := True; end; class operator TAutoPublisher.Subtract(const APublisher: TAutoPublisher; const ASubscriber: IWeakly): Boolean; begin APublisher.Publisher.Unsubscribe(ASubscriber); Result := True; end;
I think it is clear without words. We just do MyForm. MyEvents + MySubscriber ; - we subscribed. Subtracted: MyForm.MyEvents - MySubscriber ; - unsubscribed.
Source: https://habr.com/ru/post/219685/