Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Wie töte ich ein TAggregatedObject? (https://www.delphipraxis.net/177999-wie-toete-ich-ein-taggregatedobject.html)

Der schöne Günther 10. Dez 2013 11:17

Delphi-Version: XE5

Wie töte ich ein TAggregatedObject?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Denksport-Aufgabe am frühen Morgen.

Ich habe eine Klasse (nennen wir sie
Delphi-Quellcode:
TMonsterklasse
) welche die Realisierung eines Interfaces (
Delphi-Quellcode:
IMyInterface
) an eine andere Klasse (
Delphi-Quellcode:
TMyInterfaceDelegate
) delegieren soll.

Je nachdem was man haben möchte, leitet man
Delphi-Quellcode:
TMyInterfaceDelegate
nun am besten von
Delphi-Quellcode:
TAggregatedObject
oder
Delphi-Quellcode:
TContainedObject
ab. Zur Erinnerung: Ersteres leitet alle
Delphi-Quellcode:
_QueryInterface
-Anfragen auf sich wieder an die
Delphi-Quellcode:
TMonsterklasse
zurück,
Delphi-Quellcode:
TContainedObject
nicht. Beide reichen aber die ARC-Methoden
Delphi-Quellcode:
_AddRef
und
Delphi-Quellcode:
_Release
an
Delphi-Quellcode:
TMonsterklasse
zurück.

Und wo ist nun die Frage? Folgendes: Im Destruktor meiner TMonsterklasse bekomme ich das aggregierte
Delphi-Quellcode:
TMyInterfaceDelegate
nicht freigegeben da ich es mir in TMonsterklasse nur als Interface-Referenz vom Typ
Delphi-Quellcode:
IMyInterface
merke.
Das einmal als Bild im Anhang und im Folgenden als Delphicode:

Delphi-Quellcode:
IMyInterface = interface
   ['{62EB99E2-5762-41C2-AF46-D612B9BD5C01}']
   procedure someProc();
end;

//TMyInterfaceDelegate = class(TContainedObject, ISubInterface)
TMyInterfaceDelegate = class(TAggregatedObject, IMyInterface)
   public
      // controller wäre dann eine TMonsterklasse-Instanz
      constructor Create(const controller: IInterface);
      destructor Destroy(); override;

      procedure someProc();
end;

// Diese Klasse delegiert die Realisierung von IMyInterface an
// ein aggregiertes TMyInterfaceDelegate-Objekt
TMonsterklasse = class(TInterfacedObject, IMyInterface)
   private var
      mySubInterfaceDelegate: IMyInterface;

   private
      function getMyInterfaceDelegate(): IMyInterface;

   public
      constructor Create();
      destructor Destroy(); override;

   // Interface Delegation
   protected property myInterfaceRealization: IMyInterface
   read getMyInterfaceDelegate implements IMyInterface;
end;
Interessante Teile der Implementation sind

Delphi-Quellcode:
function TMonsterklasse.getMyInterfaceDelegate(): IMyInterface;
begin
   if not Assigned(mySubInterfaceDelegate) then begin
      mySubInterfaceDelegate := TMyInterfaceDelegate.Create(self);
      // _AddRef auf myRealizer wird wieder an mich selbst delegiert!
      // Da der RefCount sonst niemals Null wird, erniedrige ich ihn
      // hier künstlich wieder um eins.
      self._Release();
   
   end;

   Result := mySubInterfaceDelegate;
end;

destructor TMonsterklasse.Destroy();
begin
   // Wie zerstöre ich nun mySubInterfaceDelegate?
   
   // An den Referenzzähler komme ich nicht dran
   
   // Ist es ein TContainedObject kann ich noch zu einer Klassenreferenz
   // casten und dann den Destruktor aufrufen. Aber bei TAggregatedObject
   // wird _QueryInterface an mich delegiert und ich kann nichtmals
   // zu einer Klasse casten...
   (mySubInterfaceDelegate as TContainedObject).Free();
end;
Wie man sieht, alles andere als schön. Ich möchte allerdings vermeiden, meine Referenz auf das aggregierte Objekt in der ganz konkreten Klasse (also vom Typ
Delphi-Quellcode:
TMyInterfaceDelegate
) zu hinterlegen. Meine Idee wäre, die Referenz immerhin noch vom Typ
Delphi-Quellcode:
TAggregatedObject
zu halten. Die obige Implementation verkürzt sich somit auf
Delphi-Quellcode:
destructor TMonsterklasse.Destroy();
begin
   mySubInterfaceDelegate.Free();
end;

function TMonsterklasse.getMyInterfaceDelegate(): IMyInterface;
begin
   if not Assigned(mySubInterfaceDelegate) then
      mySubInterfaceDelegate := TMyInterfaceDelegate.Create(self);

   if not Supports(mySubInterfaceDelegate, IMyInterface, Result) then
      raise Exception.Create('Ich habe keine Ahnung was dann geschehen soll');
end;
Hier fühle ich mich aber mit der letzten Methode nicht wohl. Aber immerhin funktioniert es und alles wird auch ordentlich freigegeben. Kann mir jemand gut zureden, dass ich alles richtig mache? Oder kann man etwas anders lösen?

Uwe Raabe 10. Dez 2013 13:33

AW: Wie töte ich ein TAggregatedObject?
 
Da TAggregatedObject nicht über die Referenzzählung freigegeben wird, spricht nichts gegen eine lokale Instanz der entsprechenden Klasse innerhalb deiner Monster-Klasse. Da die Monster-Klasse für die Freigabe der Instanz verantwortlich ist, entspricht das auch dem gewünschten Design.

Gegen eine Speicherung in einer Interface-Variable spricht, daß du dir damit eine unerwünschte Referenzzählung einhandelst, die ein Freigeben der Monster-Instanz eigentlich verhindert, was du ja auch mit dem self._Release ausgleichst (müffelt halt etwas).

Es ist auch durchaus korrekt, wenn du das Feld als
Delphi-Quellcode:
mySubInterfaceDelegate: TMyInterfaceDelegate
deklarierst. Damit kannst du deine
Delphi-Quellcode:
getMyInterfaceDelegate
Funktion nämlich fasst so belassen wie sie ist.

Grundsätzlich (sprich: ich kenne bisher keine Ausnahme) kann man sagen, daß bei zusammengesetzten Objekten mit
Delphi-Quellcode:
TAggregatedObject
und
Delphi-Quellcode:
TContainedObject
immer die passenden Klassen als lokale Felder deklariert werden sollten.

Der explizite
Delphi-Quellcode:
_Release
-Aufruf sollte bei einem selbst schon eine Warnung auslösen daß etwas nicht korrekt ist. In Delphi werden
Delphi-Quellcode:
_AddRef
und
Delphi-Quellcode:
_Release
eher selten direkt aufgerufen.

Der schöne Günther 10. Dez 2013 17:54

AW: Wie töte ich ein TAggregatedObject?
 
Ich wusste doch, auf den Spezialisten zu diesem Gebiet kann ich mich verlassen :love:

Dann ist schon mal sehr sicher, dass man die einzelnen Bausteine als Klassenreferenzen, nicht als Interface anspricht.

Meine Motivation, als Typ eine Basisklasse möglichst weit oben (z.B.
Delphi-Quellcode:
TAggregatedObject
) zu nehmen war, dass ich so auch schnell die Klasse austauschen kann bzw. einfach etwas anderes aus einer Factory kommt.

Andererseits sind es ja nur sehr wenige Stellen die man anfassen müsste, wollte man die verwendete Klasse komplett austauschen. Ich werde da mal drüber schlafen...

Vielen Dank für die Antwort :thumb:


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:42 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz