Delphi-PRAXiS
Seite 1 von 4  1 23     Letzte »    

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Class Helper als Lösung für zirkuläre Unit-Referenzen (https://www.delphipraxis.net/176296-class-helper-als-loesung-fuer-zirkulaere-unit-referenzen.html)

Der schöne Günther 25. Aug 2013 19:35

Delphi-Version: XE2

Class Helper als Lösung für zirkuläre Unit-Referenzen
 
Diesiges Wetter. Zeit, wieder über zirkuläre Unit-Referenzen zu plauschen :roteyes:

Vielleicht bin ich noch zu neu in Delphi, aber ich finde das Thema ist nicht gegessen. Ich habe aktuell auch (glücklicherweise nur ein oder zwei) Fälle, in denen sich zwei Klassen gegenseitig kennen müssen. Alles direkt mit "Das ist schon Zeuge genug für schlechtes Design, mach es irgendwie anders" abzuwürgen ist mir zu billig.

Hier nun eine interessante Methode, zirkuläre Referenzen mit Class Helpern noch einmal neu anzugehen: Solving circular unit references with class helpers. Ich habe hier im Forum noch keine heißblütig geführten Diskussionen dazu gefunden. Deshalb nun dieses Thema.

Zusammenfassung: Altbewährtesbekanntes Halten einer (protected) Referenz vom Typ
Delphi-Quellcode:
TObject
, Casten auf den richtigen Typ mittels Helferklassen-Methode (oder Property).

Was haltet ihr davon? Das einzige was mich stört: Man könnte vergessen, die Helfer-Unit einzubinden und sich anschließlich wundern, wo denn der Verweis (bsp. des Hundes auf seinen Halter) steckt.

Hier einmal noch die Kurzfassung:

Delphi-Quellcode:
// Hundebesitzer.pas
uses
   Hund
;

type THundebesitzer = class
   public var
      name: String;
      hund: THund;
end;
Delphi-Quellcode:
// Hund.pas
type
   THund = class
      protected var // An 'private' kommt auch ein Helper nicht mehr ran
         besitzer_uncasted: TObject;
      public var
         name: String;
   end;
Delphi-Quellcode:
// HundHelper.pas
uses
    Hund,
   Hundbesitzer
;

type
   THundHelper = class helper for THund
      private
         function GetBesitzer: THundebesitzer;
         procedure SetBesitzer(const Value: THundebesitzer);
      public
         property besitzer: THundebesitzer read Getbesitzer write Setbesitzer;
   end;

implementation
   function THundHelper.Getbesitzer: THundebesitzer;
   begin
     if besitzer_uncasted is THundebesitzer then
      Result := besitzer_uncasted as THundebesitzer
     else
      Result := nil //Oder Exception?
     ;
   end;

silver-moon-2000 26. Aug 2013 00:10

AW: Class Helper als Lösung für zirkuläre Unit-Referenzen
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1226150)
Hier nun eine interessante Methode, zirkuläre Referenzen mit Class Helpern noch einmal neu anzugehen

Wenn ich das Thema ClassHelper richtig verstanden habe, habe ich doch durch die ClassHelper eine THund-"PseudoKlasse" in der folgenden Form
Delphi-Quellcode:
type
   THund = class
      private
         function GetBesitzer: THundebesitzer; //aus ClassHelper
         procedure SetBesitzer(const Value: THundebesitzer);//aus ClassHelper
      protected
         besitzer_uncasted: TObject;
      public
         name: String;
         property besitzer: THundebesitzer read Getbesitzer write Setbesitzer;//aus ClassHelper
   end;
Ich kann also innerhalb von THund auf den Besitzer entweder über das property direkt zugreifen oder über das ungecastete Objekt, wobei ich dann jedes Mal casten muss?

Was mich dabei stören würde, wäre die Tatsache, dass ich im Setter / Getter des properties keine zusätzliche Logik unterbringen darf (von dem Cast einmal abgesehen), wenn ich nicht will, dass diese zusätzliche Logik bei jeder Verwendung des properties ausgelöst wird.
In dem Beispiel vielleicht jetzt schwerer einsichtig, aber manchmal muss/möchte man zwischen einem Zugriff von innerhalb und außerhalb der Klasse unterscheiden (z.B. wenn man zusätzlich Aktionen durchführen möchte, wenn "von außen" (wozu properties imho gedacht sind) auf das Objekt zugegriffen wird).

Delphi-Quellcode:
procedure THundeHelper.SetBesitzer(const Value: THundebesitzer)
begin
  if Assigned(value) then
  begin
    besitzer_uncasted := value;
    if value.name = "Der schöne Günther" then
      //schlabbereNeuenBesitzerAb
    else
      //knurreNeuenBesitzerAn
  end;
end;
Wenn ich nun will, dass die Namensabfrage des neuen Besitzers nur stattfindet, wenn ich den Benutzer von außen setze und nicht von innerhalb der Klasse THund, dann würde der Zugriff von innerhalb nur über das ungecastete Object (das ich dann casten muss) und der Zugriff von außen über das property stattfinden müssen.

Oder bringt man in Settern/Gettern sowieso keine zusätzliche Logik unter? Ist das schlechter Programmierstil?

Ich hoffe, ich konnte meine Gedanken halbwegs klar formulieren

bernau 26. Aug 2013 06:00

AW: Class Helper als Lösung für zirkuläre Unit-Referenzen
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1226150)
Was haltet ihr davon? Das einzige was mich stört: Man könnte vergessen, die Helfer-Unit einzubinden und sich anschließlich wundern, wo denn der Verweis (bsp. des Hundes auf seinen Halter) steckt.

Denk bei ClassHelpern immer daran: Es kann nur einen geben.

Wenn du jetzt schon einen Classhelper für deine Klassen vergibst, kannst du keinen weiteren ClassHelper mehr für die Klasse verwenden. Classhelper sind ja eigendlich dazu gedacht, an eine Klasse etwas anzuflanschen, weil man an die Ursprungs-Unit nicht dran kommt. Du verbaust quasi anderen die Möglichkeit deine Klasse zu erweitern (ohne eine Ableitung zu schreiben).

Furtbichler 26. Aug 2013 06:27

AW: Class Helper als Lösung für zirkuläre Unit-Referenzen
 
Diese Lösung mit dem Zeiger-In-Den-Nebel (TObject) ist doch -ehrlich gesagt- Quark. Innerhalb von THund kannst Du ja gar nicht auf den Hundebesitzer zugreifen. Somit ist diese Umschiffung (eine Lösung ist das ja nicht) des Henne-Ei-Problems mittels Classhelper vielleicht ein Notbehelf, aber imho ein ziemlich einengender.

Das Problem tritt ja dann auf, wenn man eine eigentlich saubere Implementierung in unterschiedliche Units aufteilt. Wären sie in einer Unit, gäbe es das Problem nicht:
Delphi-Quellcode:
Type
  THundebesitzer = class;
  THund = class
     fHundebesitzer : THundebesitzer;
  public
     Hundebesitzer : THundebesitzer read ....;
  End;
Wieso verwendest Du nicht einfach Interfaces? Wenn man das konsequent durchzieht und keine kranken Sperenzien anstellt, gibt es auch keine Probleme.

TiGü 26. Aug 2013 07:14

AW: Class Helper als Lösung für zirkuläre Unit-Referenzen
 
Und was ist mit der Möglichkeit, in einer dritten Unit ein generisches Dictionary anzulegen, was die Zuordnung von Hund und Hundehalter speichert?
Außerdem könnte so ein Hundebesitzer leicht mehrere Hunde haben oder umgekehrt ein Hund zwei Besitzer (Herr und Frau Müller).

Furtbichler 26. Aug 2013 07:17

AW: Class Helper als Lösung für zirkuläre Unit-Referenzen
 
Zitat:

Zitat von TiGü (Beitrag 1226181)
Und was ist mit der Möglichkeit, in einer dritten Unit ein generisches Dictionary anzulegen, was die Zuordnung von Hund und Hundehalter speichert?
Außerdem könnte so ein Hundebesitzer leicht mehrere Hunde haben oder umgekehrt ein Hund zwei Besitzer (Herr und Frau Müller).

Hier geht es nicht um Design, sondern um die Lösung eines allgegenwärtigen Problems: Zirkuläre Referenzen. In diesem Fall *soll* es ja so sein, das ein Hund maximal einen Besitzer hat. Das ist sozusagen Constraint-By-Design. Bei deiner Lösung müsste man das in der Dictionary-Klasse explizit ausprogrammieren.

Tendentiell ist so eine Relationsklasse natürlich ein schöner Ansatz, wenn die Besitzverhältnisse mit Logik verbunden sind.

jaenicke 26. Aug 2013 07:46

AW: Class Helper als Lösung für zirkuläre Unit-Referenzen
 
In den meisten Fällen lässt sich das auch viel einfacher durch eine saubere Schnittstelle lösen. Denn die Frage ist doch was man von dem Objekt Hund im Objekt Besitzer machen will. Meistens kann man das auch über Events oder ähnliches lösen.
Delphi-Quellcode:
// Zum Beispiel nicht:
Hund.Bellen;
Hund.Besitzer.Schimpfen;

// sondern:
Hund.OnBellt := Besitzer.HundBelltEvent;

procedure TBesitzer.HundBelltEvent;
begin
  Schimpfen;
end;

// und dann nur:
Hund.Bellen;
// EDIT:
Interfaces lösen das Problem auch nicht, wenn man tatsächlich eine echte Referenz in beide Richtungen will. Man könnte allerdings die Referenz in einem abgeleiteten Interface unterbringen und nur das Basisinterface zurück referenzieren. Das geht bei Klassen aber auch.

Stevie 26. Aug 2013 07:48

AW: Class Helper als Lösung für zirkuläre Unit-Referenzen
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1226150)
An 'private' kommt auch ein Helper nicht mehr ran

Doch, kommt er - man muss nur Self davor schreiben.

Namenloser 26. Aug 2013 08:17

AW: Class Helper als Lösung für zirkuläre Unit-Referenzen
 
Ich packe Klassen, die sich gegenseitig benötigen, einfach in dieselbe Unit. So schlimm ist das gar nicht. Lange Units sind halt auch irgendwie der Pascal-Way... guckt euch doch nur mal die VCL an. Und zur Not könnte man immer noch Include-Dateien verwenden – jeweils Implementation und Interface getrennt. Damit bastelt man sich sowas ähnliches wie bei C++.

Lemmy 26. Aug 2013 08:24

AW: Class Helper als Lösung für zirkuläre Unit-Referenzen
 
Zitat:

Zitat von jaenicke (Beitrag 1226184)
// EDIT:
Interfaces lösen das Problem auch nicht, wenn man tatsächlich eine echte Referenz in beide Richtungen will. Man könnte allerdings die Referenz in einem abgeleiteten Interface unterbringen und nur das Basisinterface zurück referenzieren. Das geht bei Klassen aber auch.

natürlich lösen die Interfaces das Problem. Zumindest wenn man Interfaces wie Interfaces verwendet und nicht wie KLassen :-)

Grüße


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:40 Uhr.
Seite 1 von 4  1 23     Letzte »    

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