Hallo!
Es war nicht so einfach ein Titel für mein Problem zu finden, ich hoffe er ist okay.
Ich habe folgendes Problem.
Ich habe eine Datenklasse TRepo, ein Repository welches Daten aus einer Datenbank hält und das Observer-Pattern implementiert hat. Wenn also eine Änderung an den Daten stattfindet z.b durch die Datenbank selbst, werden alle Observer benachrichtig z.b eine
GUI. Die
GUI füllt dann z.B. ein Grid neu.
Dann habe ich ein
GUI-Fenster, welches ebenfalls eine Schnittstelle implementiert.
In Delphi sind ja die Schnittstellen in der Regel Referenzgezählt und sollte der Zähler auf Null fallen, wird das konkrete Objekt zerstört.
Jetzt passiert folgendes, ich hoffe ich kann es irgendwie erklären mittels Code.
Delphi-Quellcode:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs;
type
{ TGUIFenster }
TGUIFenster =
class(TGuiInterfaceBase, IDataObserver)
private
{ private declarations }
FRepo : TRepo;
FMObject: TObject;
procedure OnBeforeClose;
public
{ public declarations }
constructor Create(ASender : TObject;IDX : integer);
overload;
destructor Destroy;
override;
end;
implementation
{$R *.lfm}
{ TGUIFenster }
procedure TGUIFenster.OnBeforeClose;
begin
{
Prüfe auf Änderungen, die nicht gespeichert sind
Trenne Verbindungen
Schreibe Log
etc..
...
}
FRepo.Detach(Self);
end;
constructor TGUIFenster.Create(ASender: TObject; IDX: integer);
var
res : integer;
begin
inherited Create(ASender, IDX);
FMObject := TObject.Create;
FRepo := NewDataRepository;
FRepo.Attach(Self);
FRepo.Open;
res := 5 / 0;
//bewusste Exception löst direkt den Destructor Auf
end;
destructor TGUIFenster.Destroy;
begin
FMObject.Free;
{...
...}
FRepo :=
nil;
//nimmt die einzige bisher existierende Referenz vom GUI-Fenster
//und zerstört dieses damit. Destructor wird 2x aufgerufen.
inherited Destroy;
end;
end.
Ab Zeile " FRepo := NewDataRepository;" bis " FRepo.Open;" erstelle ein TRepo Objekt und füge die
GUI selbst als Beobachter ein und öffne das Repository. Damit erhöht sich die Referenz vom
GUI Element um 1.
Eine Zeile später verursache ich eine
Exception, in dem Fall bewusst, dadurch wird unmittelbar der Destructor aufgerufen.
Der Destrutor setzt das TRepo auf nil, damit gibt es keine Referenz mehr auf das TRepo und es wird zerstört, alles soweit ok.
Gleichzeitig wird aber auch die Referenz von meinem
GUI auf das Repo weggenommen und dann beißt sich die Katze in den Schwanz. Dadurch dass es nun keine Referenz mehr auf das
GUI gibt, wird dieses auch automatisch zerstört.
Der Destructor wird also direkt ein 2x aufgerufen, was natürlich dann in einer neuen
Exception endet, weil die Sachen ja bereits zum Teil zerstört wurden.
Kurz zum normalen Ablauf, falls sich der Leser fragt, wie ich das denn sonst freigebe ohne Probleme:
Im normalen Ablauf sagt die
GUI-Steuerung dem
GUI-Fenster per Event dass es geschlossen wird.
Es wird also ein OnBeforeClose-Event ausgelöst. In diesem Event löse ich die Referenz vom
GUI-Fenster zum Repo
mittels eines Detach auf, außerdem werden noch auf Änderungen geprüft, Verbindungen getrennt, etc. Dann löst die Steuerung die letzte Referenz auf und das
GUI-Fenster wird zerstört.
Der Destructor vom
GUI-Fenster wird also niemals von mir per Free direkt aufgerufen, sondern nur, wenn es keine Referenzen mehr gibt und auch nur über eine Steuerung.
Wie umgehe ich das Problem? Oder was muss ich ändern, damit es nicht passiert?
Lässt sich das überhaupt umgehen ohne sehr große Designänderung?
Klar ich kann es so programmieren und testen, dass im Konstruktor absolut nix schief gehen kann, aber dass kann es ja bekanntlich immer. Ich will es nur robuster machen.
Im Grunde will ich den automatischen Destructoraufruf bei der
Exception unterbinden. Da ich die
Exception abfange will ich vorher die Referenzen wegnehmen, damit der Destructor dann sauber durchläuft.