Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Observer: (https://www.delphipraxis.net/153105-observer.html)

GroHae 21. Jul 2010 09:42

Delphi-Version: 2007

Observer:
 
Hallo zusammen,

ich spiele gerad ein bischen mit dem Observer Muster rum und habe folgende Klassen erstellt:
Delphi-Quellcode:

  IBeobachter = interface
    ['{965C77DA-CA00-4915-8133-2D865B8F8182}']
    procedure Aktualisieren(iTemperatur, iFeuchtikgkeit, iDruck: real);
    procedure Anzeigen;
  end;

  ISubject = interface
     ['{EB06F989-902A-43C1-8A58-DC671744DD2E}']
     procedure RegistriereBeobachter(iIBeobachter: IBeobachter);
     procedure EntferneBeobachter(iIBeobachter: IBeobachter);
     procedure BenachrichtigeBeobachter;
  end;

  TWetterDaten = class(TInterfacedObject, ISubject)
  private
     Temperatur, Feuchtikgkeit, Druck : real;
     FLstBeobachter : TInterfaceList; // !!!!!!!!!!!
  public
     constructor Create;
     destructor Destroy; override;
     procedure RegistriereBeobachter(iIBeobachter: IBeobachter);
     procedure EntferneBeobachter(iIBeobachter: IBeobachter);
     procedure BenachrichtigeBeobachter;
     procedure MesswerteGeaendert;
     procedure SetMesswerte(iTemperatur, iFeuchtikgkeit, iDruck : real);
  end;

  TTemperaturAnzeigen = class(TInterfacedObject, IBeobachter)
  private
    FWetterDaten : ISubject;
    Temperatur, Feuchtikgkeit, Druck : real;
  public
    procedure Aktualisieren(iTemperatur, iFeuchtikgkeit, iDruck: real);
    procedure Anzeigen;
    constructor Create(iISubject : ISubject);
    destructor Destroy; override; // Zum Testen !!!!
  end;

  TFeuchtigkeitAnzeigen = class(TInterfacedObject, IBeobachter)
  private
    FWetterDaten : ISubject;
    Temperatur, Feuchtikgkeit, Druck : real;
  public
    procedure Aktualisieren(iTemperatur, iFeuchtikgkeit, iDruck: real);
    procedure Anzeigen;
    constructor Create(iISubject : ISubject);
  end;
Ich habe mir ein Formular etstellt, in welchem ich die beiden Beobachter erstelle
Die beiden Beobachter erstelle in in meinem Formular bei Programmstart:
Delphi-Quellcode:
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  FWetterDaten := TWetterDaten.Create;
  FWetterDaten.SetMesswerte(30,65,30);

  FTemperaturAnzeigen := TTemperaturAnzeigen.Create(FWetterDaten);
  FFeuchtigkeitAnzeigen := TFeuchtigkeitAnzeigen.Create(FWetterDaten);
end;

Über Buttons will ich die Beobachter "Ein/Aus Schalten"
Delphi-Quellcode:
procedure TfrmMain.Button2Click(Sender: TObject);
begin
  FWetterDaten.RegistriereBeobachter(FTemperaturAnzeigen);
end;

procedure TfrmMain.Button3Click(Sender: TObject);
begin
  FWetterDaten.RegistriereBeobachter(FFeuchtigkeitAnzeigen);
end;

procedure TfrmMain.Button4Click(Sender: TObject);
begin
  FWetterDaten.EntferneBeobachter(FTemperaturAnzeigen);
end;

procedure TfrmMain.Button5Click(Sender: TObject);
begin
  FWetterDaten.EntferneBeobachter(FFeuchtigkeitAnzeigen);
end;

procedure TfrmMain.Button6Click(Sender: TObject);
begin
  FWetterDaten.BenachrichtigeBeobachter;
end;
Mein Problem ist aber, das nach dem Entfernen des Beobachters, diese Beobachter frei gegeben wird. Das sollte aber nicht sein.
Die Instanz des Beobachters habe ich doch in meinem Formular erstellt. Warum wird sie zerstört, nachdem ich sie aus der InterfaceList raus nehme?

Hier noch mein mein Subject und ein Beobachter:
Delphi-Quellcode:
{ TWetterDaten }

constructor TWetterDaten.Create;
begin
  FLstBeobachter := TInterfaceList.Create;
end;

destructor TWetterDaten.Destroy;
begin
  inherited;
end;

procedure TWetterDaten.BenachrichtigeBeobachter;
var
  i : integer;
begin
  for i := 0 to FLstBeobachter.Count - 1 do
    IBeobachter(FLstBeobachter[i]).Aktualisieren(Temperatur, Feuchtikgkeit, Druck);
end;

procedure TWetterDaten.EntferneBeobachter(iIBeobachter: IBeobachter);
begin
  FLstBeobachter.Remove(iIBeobachter);
end;

procedure TWetterDaten.RegistriereBeobachter(iIBeobachter: IBeobachter);
begin
  FLstBeobachter.Add(iIBeobachter);
end;


procedure TWetterDaten.SetMesswerte(iTemperatur, iFeuchtikgkeit, iDruck: real);
begin
  Temperatur := iTemperatur;
  Feuchtikgkeit := iFeuchtikgkeit;
  Druck := iDruck;
  MesswerteGeaendert;
end;

procedure TWetterDaten.MesswerteGeaendert;
begin
  BenachrichtigeBeobachter;
end;
Delphi-Quellcode:
procedure TTemperaturAnzeigen.Aktualisieren(iTemperatur, iFeuchtikgkeit, iDruck: real);
begin
  Temperatur := iTemperatur;
  Feuchtikgkeit := iFeuchtikgkeit;
  Druck := iDruck;
  Anzeigen;
end;

procedure TTemperaturAnzeigen.Anzeigen;
begin
  ShowMessage('Aktuelle Wetterbedingungen: Temperatur:' + format('%f',[Temperatur]));
end;


constructor TTemperaturAnzeigen.Create(iISubject : ISubject);
begin
  FWetterDaten := iISubject;
end;


destructor TTemperaturAnzeigen.Destroy;
begin
  ShowMessage('Schade');
  inherited;
end;

himitsu 21. Jul 2010 09:58

AW: Observer:
 
Interfaces haben eine Referenzzählung.
Wenn keine Interfaceinstanzen mehr existieren, dann wird das Objekt freigegeben ... das ist der Sinn von Interfaces.

PS: aus diesesm Grund mischt man auch Interfacezugriffe und Objektzugriffe nicht miteinander, da es so schnell mal zu Problemen kommen kann.

Also wenn das Interface/Objekt noch nicht zersört werden soll, dann mußt du weiterhin eine Instanz davon irgendwo gespeichert/abgelegt haben.

GroHae 21. Jul 2010 10:17

AW: Observer:
 
Hi,

Danke erst mal!

Zitat:

Also wenn das Interface/Objekt noch nicht zersört werden soll, dann mußt du weiterhin eine Instanz davon irgendwo gespeichert/abgelegt haben.
Stimmt! Un genau deswegen habe ich in meinem Hauptformular die instanz erstellt
Delphi-Quellcode:
 TfrmMain = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
  private
    FWetterDaten : TWetterDaten;
    FTemperaturAnzeigen : TTemperaturAnzeigen; // ... eine Instanz davon irgendwo gespeichert/abgelegt ..... Oder nicht ???
    FFeuchtigkeitAnzeigen : TFeuchtigkeitAnzeigen;
  public
    { Public-Deklarationen }
  end;

.....

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  FWetterDaten := TWetterDaten.Create;
  FTemperaturAnzeigen := TTemperaturAnzeigen.Create(FWetterDaten);
  FFeuchtigkeitAnzeigen := TFeuchtigkeitAnzeigen.Create(FWetterDaten);
end;
Anscheinend habe ich da was falsch verstanden. Aber was?
Ich gehe davon aus, durch FTemperaturAnzeigen im Formular ich die Instanz erst "verliere" wenn ich das Formular zerstöre

schlecki 21. Jul 2010 10:33

AW: Observer:
 
Zitat:

Zitat von GroHae (Beitrag 1036562)
Stimmt! Un genau deswegen habe ich in meinem Hauptformular die instanz erstellt
Delphi-Quellcode:
 TfrmMain = class(TForm)
   { ... }
    FTemperaturAnzeigen : TTemperaturAnzeigen; // ... eine Instanz davon irgendwo gespeichert/abgelegt ..... Oder nicht ???
    FFeuchtigkeitAnzeigen : TFeuchtigkeitAnzeigen;
  public
    { Public-Deklarationen }
  end;

.....
Anscheinend habe ich da was falsch verstanden. Aber was?
Ich gehe davon aus, durch FTemperaturAnzeigen im Formular ich die Instanz erst "verliere" wenn ich das Formular zerstöre

Nicht falsch verstanden, nur falsch deklariert ;)
Deine Variablen sollten auch Interfaces sein, also zum Beispiel IBeobachter...

Delphi-Quellcode:
{ ... }
    FTemperaturAnzeigen : IBeobachter; // ... eine Instanz davon irgendwo gespeichert/abgelegt ..... Oder nicht ???
    FFeuchtigkeitAnzeigen : IBeobachter;
  public

GroHae 21. Jul 2010 10:41

AW: Observer:
 
**Schuppen von den Augen fallen**

Mist habe ich übersehen. Stimmt. Es heißt ja auch immer in meinem Buch
Zitat:

Programmieren Sie auf eine Schnittstelle, nicht auf eine Implementierung
1000 Danke! :-D

himitsu 21. Jul 2010 10:48

AW: Observer:
 
Ich sagte doch "nicht interfaces mit Objekten mischen" :warn:

Objekte = keine Referenzzählung
Interfaces = ja

TTemperaturAnzeigen ist das Objekt und wird demnach nicht mitgezählt.

hier gibt es also nur 2-3 Lösungen:
- entweder du meldest beim Interface (._AddRef) diese Referenz an
- oder besser, du nimmst hier auch statt TTemperaturAnzeigen das IBeobachter.
- oder du umgehst die Referenzzählung (siehe #7 Uwe Raabe, bzw. implementierst dir eine eigene "Referenzzählung", welche auf deine Bedürfnisse abgestimmt ist)

Uwe Raabe 21. Jul 2010 10:49

AW: Observer:
 
So, wie du es machst, steckst du in einem Dilemma: um der Interface-Referenzzählung zu genügen, solltest du nur Interfaces als Instanz-Variablen haben, aber ein ISubject hat leider nicht die Methoden und Eigenschaften eines TWetterDaten, was die Manipulation einer Interface-Instanz verhindert.

Ausweg 1: Du deklarierst zu jeder Klasse ein passendes Interface, das von ISubject abgeleitet ist. => Viel Schreiberei und meher Aufwand bei Änderungen, aber sauber bezüglich Referenzzählung.

Ausweg 2: Du leitest statt von TInterfacedObject von TInterfacedPersistent ab. Damit umgehst due die Referenzzählung und kannst wieder Instanzen der Klassen (statt der Interfaces) verwenden. Du musst aber selber dafür sorgen, daß die Instanzen in der richtigen Reihenfolge freigegeben werden, aber das sollte einem Delphianer ja nicht fremd sein.

Da TInterfaceList von TInterfacedObject abgeleitet ist, musst du auch eine IInterfaceList-Instanz verwalten, sonst kracht es irgendwann. Also FLstBeobachter als IInterfaceList deklarieren!

GroHae 21. Jul 2010 11:14

AW: Observer:
 
meinst du mit

Zitat:

Ich sagte doch "nicht interfaces mit Objekten mischen"
dass ich nicht
Delphi-Quellcode:
...
  private
    FWetterDaten : TWetterDaten;
    FTemperaturAnzeigen : IBeobachter;
    FFeuchtigkeitAnzeigen : IBeobachter;
  public
...
sagen soll? :oops:

und wenn ich das von Uwe Raabe lese, da erscheint es mir dann (fast) ratsamer mit abstrakten Klassen zu arbeiten.

Uwe Raabe 21. Jul 2010 11:27

AW: Observer:
 
Zitat:

Zitat von GroHae (Beitrag 1036573)
Delphi-Quellcode:
...
  private
    FWetterDaten : TWetterDaten;
    FTemperaturAnzeigen : IBeobachter;
    FFeuchtigkeitAnzeigen : IBeobachter;
  public
...

FWetterDaten sollte IWetterDaten werden (Ausweg 1) oder von TInterfacePersistent abgeleitet werden.

Zitat:

Zitat von GroHae (Beitrag 1036573)
und wenn ich das von Uwe Raabe lese, da erscheint es mir dann (fast) ratsamer mit abstrakten Klassen zu arbeiten.

Also, so schwer ist das wirklich nicht. Man muss halt wissen, was man tut. Interfaces sind eine feine Sache und machen den Code oft viel übersichtlicher, aber ich verzichte in den meisten Fällen auf die automatische Referenzzählung und kümmere mich selbst um die Freigabe der Objekte.

David Martens 21. Jul 2010 11:41

AW: Observer:
 
So ein Problem hatten wir auch. Wir haben es über TNotifyEvent gelöst.
Dein
Delphi-Quellcode:
FLstBeobachter : TInterfaceList;
machst du zu einer Liste von TNotifyEvents.

BenachrichtigeBeobachter; Schickt dann nur die Nachricht in den eigentlichen TTemperaturAnzeigen... dort kannst du den Sender dann auf TWetterDaten prüfen, carsten und die relevanten Daten benutzen.

Gruß David


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:30 Uhr.
Seite 1 von 2  1 2      

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