AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Programmieren allgemein Delphi Observer-Pattern Implementation
Thema durchsuchen
Ansicht
Themen-Optionen

Observer-Pattern Implementation

Ein Thema von Ghostwalker · begonnen am 10. Nov 2016 · letzter Beitrag vom 11. Nov 2016
Antwort Antwort
Ghostwalker

Registriert seit: 16. Jun 2003
Ort: Schönwald
1.299 Beiträge
 
Delphi 10.3 Rio
 
#1

Observer-Pattern Implementation

  Alt 10. Nov 2016, 14:40
Im Zuge eines etwas größeren Projekts, hat mich stahli (dicken Dank für deine Gedult und Mühe an der Stelle) auf die Idee gebracht, für meine Anwendung das Observer-Pattern zu nutzen.

Dazu hatte ich einige Vorraussetzungen:

a) Die Implementierung der notwendigen Funktionen sollte möglichs eigentständig sein

Da einiges an Objekten zusammenkommt, wollte ich vermeiden, jedesmal das gleiche
zu machen. Andererseits müssen bei den Unterschiedlichen konkreten Objekten
aber auch unterschiedliche Daten übertragen werden.

b) Da das o.g. Projekt später auch über ein Plugin-System erweiterbar werde wird,
muss auch gewährleistet sein, das ich ggf. Elemente aus einer externen DLL bekomme.


b heißt also Interfaces, da das der mir einzig bekannte Weg ist, sowas wie Objekte
aus einer DLL zu bekommen, bzw anzusprechen.

a heißt, Basisklassen die die Daten als Pointer liefern.

Hier will ich nu meine Implementation präsentieren:

Interface

Delphi-Quellcode:
TYPE
  ISubject = INTERFACE;

  IListener = INTERFACE
    Procedure RegisterMe(Const aSubject:ISubject);
    Procedure UnregisterMe(Const aSubject:ISubject);
    procedure Notification(const data:Pointer);
  END;

  ISubject = Interface
     Procedure RegisterListener(const obj:IListener);
     Procedure UnregisterListener(Const obj:IListener);
     Procedure NotifyListener(const data : Pointer);
  End;
Basisklassen:

Delphi-Quellcode:
unit BaseClasses;

interface
uses
  windows,classes,contnrs,BaseInterfaces;

TYPE
  TBaseSubject = class;

  TBaseListener = class(TInterfacedPersistent,IListener)
  private
  protected
  published
  public
    Procedure RegisterMe(Const aSubject:ISubject);
    Procedure UnregisterMe(Const aSubject:ISubject);
    //Wird erst in der Ableitung befüllt.
    procedure Notification(const data:Pointer);virtual;abstract;
  end;

  TBaseSubject = Class(TInterfacedPersistent,ISubject)
  PRIVATE
     flist : TList;
     procedure ClearList;
  PROTECTED
  PUBLISHED
     Constructor Create;
     Destructor Destroy;override;
  PUBLIC
     Procedure RegisterListener(Const obj:IListener);
     Procedure UnregisterListener(Const obj:IListener);
     Procedure NotifyListener(const data : Pointer);
  End;

implementation

{ TBaseListener }

procedure TBaseListener.RegisterMe(aSubject: ISubject);
begin
  aSubject.RegisterListener(self);
end;

procedure TBaseListener.UnregisterMe(aSubject: ISubject);
begin
  aSubject.UnregisterListener(self);
end;

{ TBaseSubject }

procedure TBaseSubject.ClearList;
begin
  while (flist.count > 0) do
    flist.Delete(0);
end;

constructor TBaseSubject.Create;
begin
  flist := TList.create;
end;

destructor TBaseSubject.Destroy;
begin
  ClearList;
  flist.free;
  inherited;
end;

procedure TBaseSubject.NotifyListener(const data:pointer);
var
  i : integer;

begin
  for I := 0 to flist.count - 1 do
    IListener(flist.items[i]).Notification(data);
end;

procedure TBaseSubject.RegisterListener(obj: IListener);
begin
  flist.Add(Pointer(obj));
end;

procedure TBaseSubject.UnregisterListener(obj: IListener);
begin
  flist.Remove(Pointer(obj));
end;
und schließlich meine "Realen" Klassen aus dem Testprojekt:

Delphi-Quellcode:
uses
  windows,classes,BaseClasses;

TYPE

  TRealListener = Class(TBaseListener)
    PRIVATE
    PROTECTED
    PUBLIC
      procedure Notification(const data:pointer);override ;
    PUBLISHED
  End;

  TRealSubject = Class(TBaseSubject)
    PRIVATE
      fcaption : string;
    procedure setCaption(const Value: string);
    PROTECTED
    PUBLIC
    PUBLISHED
      property Caption:string read fcaption write setCaption;
  End;
implementation
uses
  unit28;

{ TRealListener }

procedure TRealListener.Notification(const data: pointer);
begin
  form28.memo1.lines.append(string(data^));
end;

{ TRealSubject }


procedure TRealSubject.setCaption(const Value: string);
begin
  fcaption := Value;
  NotifyListener(@fcaption);
end;

end.
(unit28 is lediglich die Form der Application mit Buttons und einem Memo drauf )

Außer dem Einsatz von Generics, fällt mir im Moment keine Optimierung mehr ein (und da ich
Turbo Delphi einsetze, geht das im Moment nicht )

Was haltet ihr davon ?
Uwe
e=mc² or energy = milk * coffee²
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.196 Beiträge
 
Delphi 10 Seattle Enterprise
 
#2

AW: Observer-Pattern Implementation

  Alt 10. Nov 2016, 15:34
Als ich mit Delphi angefangen habe dachte ich auch erst das Observer-Pattern so umsetzen zu müssen, da ich es in Java (vor Java 8) so gelernt hatte.

Vielleicht habe ich es mir jetzt nicht genau angesehen, aber das Interface "Listener" ist doch eigentlich überflüssig, oder? Warum muss mein Objekt eine bestimmte Schnittstelle implementieren, nur um etwas mitzubekommen? Man könnte dem Subject stattdessen eine procedure register(onNotification: TProc) verpassen mit welcher jeder auf dem Observable einmal z.B. sagen kann

Delphi-Quellcode:
someObservable.register(
    procedure()
    begin
        ShowMessage(someObservable.someValue);
    end
);
Man könnte sich auch mehrmals registrieren. Sehe ich mit dieser Implementation nicht wie das ginge.

Das ist glücklicherweise wie man das in letzter Zeit auch häufig ab Java 8 und C++ 11 sieht. Ob das "Turbo Pascal" auch kann weiß ich nicht, ich weiß noch nichtmal was das ist.


Allen Bauer hier auch mal so etwas in die Richtung gebaut:
https://community.embarcadero.com/bl...generics-38865
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.629 Beiträge
 
Delphi 12 Athens
 
#3

AW: Observer-Pattern Implementation

  Alt 10. Nov 2016, 15:57
ich weiß noch nichtmal was das ist.
Das nennt sich Anonyme Methode.

Es ist tatsächlich so, daß sich viele Design-Patterns mittels moderner Sprachkonstrukte in einem aktuellen Delphi wesentlich schlanker und eleganter umsetzen lassen, als es in den Lehrbüchern beschrieben ist.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.196 Beiträge
 
Delphi 10 Seattle Enterprise
 
#4

AW: Observer-Pattern Implementation

  Alt 10. Nov 2016, 16:29
ich weiß noch nichtmal was das ist.
Das nennt sich Anonyme Methode.
Was "Turbo Pascal" ist, meinte ich


Java hat so etwas ja erst extrem spät bekommen, viele Observer-Pattern Implementationen in Delphi sehen so aus als hätte man sie aus Java 1:1 übernommen



Warum soll sich ein Objekt bei einem anderen mehrmals für die gleiche Notification registrieren ?????
Unverhofft kommt oft
Ich meinte aber eher: Wenn sich ein Objekt bei zwei Observables vom gleichen Typ registrieren will - Dann rufen beide zwangsläufig die gleiche Methode auf, obwohl man das sicher nicht will.
  Mit Zitat antworten Zitat
Ghostwalker

Registriert seit: 16. Jun 2003
Ort: Schönwald
1.299 Beiträge
 
Delphi 10.3 Rio
 
#5

AW: Observer-Pattern Implementation

  Alt 10. Nov 2016, 16:42
Unverhofft kommt oft
Ich meinte aber eher: Wenn sich ein Objekt bei zwei Observables vom gleichen Typ registrieren will - Dann rufen beide zwangsläufig die gleiche Methode auf, obwohl man das sicher nicht will.

Doch, genau das will ich, zumindest in dem geplanten Anwendungsfall. Der Unterschied besteht hier lediglich in den übergebenen Daten bei der Notification (daher auch Pointer !)
Uwe
e=mc² or energy = milk * coffee²
  Mit Zitat antworten Zitat
Ghostwalker

Registriert seit: 16. Jun 2003
Ort: Schönwald
1.299 Beiträge
 
Delphi 10.3 Rio
 
#6

AW: Observer-Pattern Implementation

  Alt 10. Nov 2016, 18:48
So dale, noch mal ein Update.

Hab das ganze nochmal komplet umgebaut. Source im Anhang.

Achtung hab da FastMM4 eingebunden, um zu checken ob Memory-Leaks entstehen

Generics wären schön, insbesondere bei der Methodlist bzw. deren Nachfahren. Habsch aber net.

Jetzt ist wieder euer Meinung gefragt
Angehängte Dateien
Dateityp: zip Observertest.zip (4,0 KB, 11x aufgerufen)
Uwe
e=mc² or energy = milk * coffee²
  Mit Zitat antworten Zitat
Ghostwalker

Registriert seit: 16. Jun 2003
Ort: Schönwald
1.299 Beiträge
 
Delphi 10.3 Rio
 
#7

AW: Observer-Pattern Implementation

  Alt 10. Nov 2016, 16:22
Als ich mit Delphi angefangen habe dachte ich auch erst das Observer-Pattern so umsetzen zu müssen, da ich es in Java (vor Java 8) so gelernt hatte.

Vielleicht habe ich es mir jetzt nicht genau angesehen, aber das Interface "Listener" ist doch eigentlich überflüssig, oder?
Wie oben erwähnt, hab ich die Interfaces deshalb gemacht, damit auch später von Externer seite (z.B. Plugin) das ganze genutzt werden kann. Damit stell ich letztlich sicher, das die App und das Plugin vom gleichen sprechen


Warum muss mein Objekt eine bestimmte Schnittstelle implementieren, nur um etwas mitzubekommen? Man könnte dem Subject stattdessen eine procedure register(onNotification: TProc) verpassen mit welcher jeder auf dem Observable einmal z.B. sagen kann
hmm....was die bisherige Funktionalität betrifft, hast du Recht, da würde auch eine Prozedur/Methode
reichen.

Man könnte sich auch mehrmals registrieren. Sehe ich mit dieser Implementation nicht wie das ginge.
Warum soll sich ein Objekt bei einem anderen mehrmals für die gleiche Notification registrieren ?????

Das ist glücklicherweise wie man das in letzter Zeit auch häufig ab Java 8 und C++ 11 sieht. Ob das "Turbo Pascal" auch kann weiß ich nicht, ich weiß noch nichtmal was das ist.

Allen Bauer hier auch mal so etwas in die Richtung gebaut:
https://community.embarcadero.com/bl...generics-38865
Ähm....TD ist nicht Turbo Pascal sonder Turbo Delphi (letztlich BDS2006). Und ja, Prozedure/Function's als Parameter übergeben funktioniert schon seit Turbo Pascal 5 (gute alte DOS-Zeit), wenn auch nicht in der Variante.

Das Konzept anonymer Funktionen kenn ich von js und anderen Sprachen. Und auch wie "verpönt" sie sind
Uwe
e=mc² or energy = milk * coffee²

Geändert von Ghostwalker (10. Nov 2016 um 16:28 Uhr)
  Mit Zitat antworten Zitat
Devstructor

Registriert seit: 7. Sep 2016
4 Beiträge
 
FreePascal / Lazarus
 
#8

AW: Observer-Pattern Implementation

  Alt 11. Nov 2016, 09:33
Bitte

Das ist aber gar nicht nötig, sondern eher unschön. Die TInterfacedList wird dir nicht einfach zerstört. Dafür ist es wichtig zu wissen, wann Interfaces mit Referenzenzähler zerstört werden.

Als erstes muss der Referenzzähler aktiv sein. Bei einem TComponent wurde das zum Beispiel deaktiviert, doch die TInterfacedList ist abgeleitet von TInterfacedObject und dort werden die Referenzen gezählt. Es scheint also, als wäre die TInterfacedList gefährlich, dem ist aber nicht so.

Das Objekt wird zerstört, wenn die Referenz inkrementiert wurde und danach wieder den RefCount 0 erreicht. Wenn du den RefCount nicht erhöhst, wird auch nichts zerstört.
Um jetzt das TMBSubject zu zerstören, müsste man sich blöd anstellen. Man müsste nämlich auf die TInterfaceList mit einer Interface-Referenz zugreifen. Sprich man müsste die Liste über IInterfaceList steuern. Das ist einerseits daneben, denn man hat ja die Liste als Objekt, da muss man nicht noch eine Referenz eines Interfaces ansprechen, andererseits ist das auch nicht möglich, denn die InterfacedList ist im private Teil und alle Operationen auf dieser Liste werden in TMBSubject erledigt.

Folglich wird dir nicht einfach irgendwas zerstört und das Ganze ist sicher im Gebrauch, sonst hätte die TInterfacedList überhaupt keine Existenzberechtigung.

Wenn man die Liste zerstören möchte, müsste man in TMBSubject soetwas machen:
Delphi-Quellcode:
procedure TMBSubject.ClearObservers;
var
  MeineListeAlsInterfacec: IInterfaceList;
begin
  if Supports(FObservers,IInterfaceList,MeineListeAlsInterfacec) then
  begin
    MeineListeAlsInterfacec.Clear;
  end;
end;
Anstelle von diesem ,,normalen" Vorgehen
Delphi-Quellcode:
procedure TMBSubject.ClearObservers;
begin
  FObservers.Clear;
end;
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:50 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz