Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...? (https://www.delphipraxis.net/193391-die-alte-leier-zirkulaere-referenzen-aber-warum-nicht-so.html)

hzzm 25. Jul 2017 06:34

Delphi-Version: 10 Seattle

Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Ich hab hier schon etliche Beitraege gesucht und gefunden, die sich damit befassen, wie man Zirkulaere Unit-Referenzen verhindert.

Ich befinde mich in einer Situation, in der eine zirkulaere Referenz unvermeidbar ist und kam mit einer leicht anderen Loesung hinterm Ofen hervor.

Vielleicht laufe ich damit spaeter in eine Sackgasse, oder ihr kennt andere Gruende warum ich das so nicht machen sollte.

Die Loesung, die ich am oeftesten sehe, beinhaltet ein Umgehen der Rueck-Referenz mit Events. Aber warum eigentlich Events?
Kann ich nicht genauso gut die Funktionen in einer Proxy-unit anlegen und callen?
Delphi-Quellcode:
// MyMain.pas
uses
  UnitX, MyMainProxy;

type
  TMyMain = class(TForm)
    StatusEdit: TEdit;
    procedure SetGUIStatusOn;
  public
    FormCreate(Sender: TObject)
  end;
[...]

procedure TMyMain.FormCreate(Sender: TObject);
begin
  MMSetGUIValue := SetGUIStatusOn;
end;

procedure TMyMain.SetGUIStatusOn;
begin
  StatusEdit.Text := 'PowerMonger status ON!';
end;
Delphi-Quellcode:
// MyMainProxy.pas
unit MyMainProxy;

interface

var
  MMSetGUIValue: Procedure of Object;

implementation

end.
Delphi-Quellcode:
// UnitX.pas (zirk. Ref. <-> MyMain benoetigt)
uses
  MyMainProxy;
 
procedure UnitX.PerformHCoreOperation;
begin
  MMSetGUIValue;
end;

Headbucket 25. Jul 2017 07:19

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Hallo hzzm,

wenn ich dich richtig verstehe, dann versuchst du eine einfache Callback-Funktionalität zu realisieren, richtig? Du möchtest also in UnitX.pas deine MainForm aktualisieren.
Was an deinem Beispiel ungünstig ist, ist die globale Variable in der Unit MyMainProxy. Diese könnte von jeder Stelle aus dem Programm überschrieben werden und dann wunderst du dich vllt später, weshalb etwas nicht funktioniert.

Eine einfache andere Möglichkeit wäre z.B. folgende:
Du erstellst eine Klasse in UnitX.pas, welche das Feld "FMMSetGUIValue: Procedure of Object;" enthält (public)
In der MyMain.pas erzeugst du diese Klasse und weißt dem Feld "FMMSetGUIValue" die Funktion "SetGUIStatusOn" zu.
Nun kannst du in UnitX.pas an jeder beliebigen Stelle FMMSetGUIValue aufrufen. Vergiss aber nicht vorher mit Assigned zu prüfen, ob dem Feld auch wirklich eine Funktion zugewiesen wurde.

Grüße
Headbucket

hzzm 25. Jul 2017 07:36

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Wie kann ich denn auf Deine oder meine Art Parameter uebergeben?
Wenn ich ein Objekt als "Procedure of Object" initialisiere, funktioniert keine Syntax um einen Parameter mit anzugeben; MMSetGUIValue(Name: String);

Beim Zuweisen im FormCreate geht's auch nicht:
Code:
MMSetGUIValue := SetGUIStatusOn(Name: String);
"')' erwartet, aber ':' gefunden.

Uwe Raabe 25. Jul 2017 07:50

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Zitat:

Zitat von hzzm (Beitrag 1377374)
Wie kann ich denn auf Deine oder meine Art Parameter uebergeben?

Delphi-Quellcode:
type
  TMySetGUIValueProc = procedure(const Value: string) of object;
var
  MMSetGUIValue: TMySetGUIValueProc = nil;



procedure UnitX.PerformHCoreOperation;
begin
  MMSetGUIValue('Hallo MainForm!');
end;

Entspricht ungefähr diesem Vorgehen: How to access delphi function at DPR scope

hzzm 25. Jul 2017 08:21

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Danke Euch beiden.
Funktioniert wunderbar, auch in meinem (fraglichen) Original Schnipsel:
Delphi-Quellcode:
unit StatusProxy;

interface

var
  STFBenutzerEinstellen: Procedure(Name: String) of Object;
  STFGUILeeren: Procedure of Object;

implementation

end.

freimatz 25. Jul 2017 16:31

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Dem habe ich nichts hinzuzufügen außer bei einem Punkt. Folgendes reizt mich zum Widerspruch:
Zitat:

Zitat von hzzm (Beitrag 1377371)
Ich befinde mich in einer Situation, in der eine zirkulaere Referenz unvermeidbar ist ...

Ich behaupte eine zirkulaere Referenz kann man immer vermeiden.

Delphi-Laie 25. Jul 2017 18:31

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Zitat:

Zitat von freimatz (Beitrag 1377451)
Dem habe ich nichts hinzuzufügen außer bei einem Punkt. Folgendes reizt mich zum Widerspruch:
Zitat:

Zitat von hzzm (Beitrag 1377371)
Ich befinde mich in einer Situation, in der eine zirkulaere Referenz unvermeidbar ist ...

Ich behaupte eine zirkulaere Referenz kann man immer vermeiden.

Das ist eine Fragestellung, die m.E. die theoretische Informatik berührt.

Wüßte ich auch gern.

Was sagen die studierten Informatiker dieses Forums dazu?

jaenicke 25. Jul 2017 19:37

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Man kann eine formale zirkuläre Referenz immer durch andere Konstrukte ersetzen. Der Zugriff in beide Richtungen bleibt aber trotzdem erhalten. Sei es über Callbacks oder durch Entkoppelung mit Interfaces.
(Schaust du in den Callstack siehst du in beiden Richtungen die Zugriffe.)

Interfaces haben den Vorteil, dass man auf der Konsumentenseite, sprich bei der Verwendung des Interfaces, keinerlei Bezug auf die Klasse hat, die dieses Interface implementiert. Trotzdem kann man alle im Interface veröffentlichten Methoden frei verwenden.

Bei Callbacks oder Events wie hier im Thread diskutiert trennt man die einzelnen Teile der Implementierung weniger und ist nicht so flexibel. Für einfache Fälle reicht das aber vollkommen aus.

Fritzew 25. Jul 2017 21:11

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Zitat:

Interfaces haben den Vorteil, dass man auf der Konsumentenseite, sprich bei der Verwendung des Interfaces, keinerlei Bezug auf die Klasse hat, die dieses Interface implementiert. Trotzdem kann man alle im Interface veröffentlichten Methoden frei verwenden.
Das unterschreibe ich sofort. Ganz abgesehen davon hat man damit effektiv die Möglichkeit einzelne Code Abschnitte sauber testen zu können. Ich habe die letzten 18 Monate damit zugebracht ein riesiges Projekt (3 Millionen Zeilen) zu refactorieren. Der Business Code, nicht die GUI! ist jetzt zu ca 98 % unter Test. Ich liebe Interfaces!!! Erzählt das aber nicht meiner Frau:-D

himitsu 26. Jul 2017 02:06

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Statt Interfaces gibt es auch die Möglichkeiten des OOP.
Also Vererbung und einen Vorfahren mit abstrakten Methoden und Feldern/Variablen verwenden.

Vom Prinzip her also wie mit Interfaces und Rückreferenzierung der Instanzen.

Durch Rückreferenzierung von Interfaces/Objekte/Callbacks in die andere Unit oder in eine gemeinsam genutzt Unit kann man solche verketteten Unitreferenzen auflösen.
Zirkuläre Unitreferenzen sind aber auch nichts anderes, als eine Rückreferenzierung auf aktive Inhalte der unter Implementation eingebunden Unit, welche sich ebenfalls zur Laufzeit erst auflöst.

Statt statischer Verbindungen kann man auch erstmal komplett lose entwickeln, ohne dass sich Beide kennen, und die gegenseitigen Referenzierungen erst zur Laufzeit aufbauen, über systemglobale Objekte (FindWindow, NamedPipe, NamedMMF, GetProcAdress usw.)
Aber prizipiell ist es auch wieder das Selbe, nur dass hier die Rückreferenzierung nicht innerhalb der Delphi-Units, sondern über die gemeinsam genutzte Dinge aus System-DLLs erledigt wird.

EWeiss 26. Jul 2017 05:44

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Zitat:

@Fritzew Das unterschreibe ich sofort.
Ich auch ;)

Nachdem du mir dabei geholfen hast mit den Interfaces zurecht zu kommen arbeite ich auch sehr gerne damit.
Da ist man wirklich sehr flexibel.

gruss

hzzm 26. Jul 2017 07:00

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Zitat:

Zitat von EWeiss (Beitrag 1377484)
Nachdem du mir dabei geholfen hast mit den Interfaces zurecht zu kommen

Gibt es zu Deinem Lern-Prozess Threads hier?
Ich moechte mir diese Technik auch aneignen, finde aber leider keine interface Beispiele, die genau fuer diesen Zweck angelegt sind.
Ich brauch das einfach mal schwarz auf weiss, minimal und vollstaendig.

jaenicke 26. Jul 2017 07:21

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Ein minimales Beispiel:
Delphi-Quellcode:
// MyMain.pas
uses
  UnitX, MainInterface;

type
  TMyMain = class(TForm, IMainInterface)
    StatusEdit: TEdit;
  public
    procedure SetStatusMessage(const AValue: string);
  end;

[...]

procedure TMyMain.SetStatusMessage(const AValue: string);
begin
  StatusEdit.Text := AValue;
end;

// Aufruf:
PerformHCoreOperation(Self);
Delphi-Quellcode:
unit MainInterface;
...
type
  IMainInterface = interface
  ['{2A2A7B02-5612-44C4-8A76-D90C857C36C7}']
    procedure SetStatusMessage(const AValue: string);
  end;
Delphi-Quellcode:
unit UnitX;

uses
  MainInterface;

procedure PerformHCoreOperation(const AStatusInterface: IMainInterface);
begin
  AStatusInterface.SetStatusMessage('PowerMonger status ON!');
end;
Das ist nur ein minimales Beispiel und eine Trennung des Formulars von der Klasse, die IMainInterface implementiert wäre durchaus wünschenswert, aber für den Anfang reicht das so vollkommen aus.

Die GUID (im Interface, hier ['{2A2A7B02-5612-44C4-8A76-D90C857C36C7}']) dient dazu das Interface eindeutig zu identifizieren. Du kannst diese mit Strg + Shift + G in Delphi erzeugen.

bra 26. Jul 2017 09:11

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Ist die GUID für das Interface eigentlich zwingend notwendig, wenn man das nur in seinem eigenem Projekt verwendet?

Fritzew 26. Jul 2017 09:14

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Zitat:

Ist die GUID für das Interface eigentlich zwingend notwendig, wenn man das nur in seinem eigenem Projekt verwendet?
Nicht zwingend aber manches geht nicht ohne. Z.B Supports, cast mit as oder auch der is Operator.
Um Problemen aus dem Weg zu gehen würde ich immer eine GUID empfehlen

freimatz 26. Jul 2017 14:28

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Was habe ich da nur losgetreten? :shock:

[QUOTE=Fritzew;1377492]
Zitat:

Um Problemen aus dem Weg zu gehen würde ich immer eine GUID empfehlen
Und der Haarspalter meldet sich wieder: :twisted:
bei interfaces mit generics rate ich entschieden davon ab.

Fritzew 26. Jul 2017 14:35

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Zitat:

Und der Haarspalter meldet sich wieder:
bei interfaces mit generics rate ich entschieden davon ab.
Ja die sind wirklich ein Spezialfall ....

jaenicke 26. Jul 2017 15:30

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Um aus dem Skript bestimmte Interfaces anfragen zu können, müssen diese bei uns alle eine GUID haben. Aus dem Grund sind generische Interfaces bei uns immer noch einmal abgeleitet und dort mit einer GUID versehen.

Rollo62 27. Jul 2017 06:04

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
+1 Guter Vorschlag :lol:

Bislang habe ich generische Interfaces vermieden um Probleme zu Vermeiden ...

Rollo

freimatz 27. Jul 2017 12:19

AW: Die alte Leier: Zirkulaere Referenzen, aber warum nicht so: ...?
 
Machen wir auch so. Zum Beispiel:

Delphi-Quellcode:
  IViewValueBase = interface(IViewBase)
    ['{3EF209C2-81FD-44C6-B231-328BA2FAABE5}']
    procedure RegisterHostValidation(const p_ValidationMethod: THostPropertyValidationDelegate);
    procedure ClearRegisteredHostValidation();
  end;

  IViewValueBase<T> = interface(IViewValueBase)
    function GetValue(): T;
    property Value: T read GetValue;
  end;

  IViewValue<T> = interface(IViewValueBase<T>)
    procedure SetValue(const p_Value: T);
    property Value: T read GetValue write SetValue;
  end;

  IViewValueBoolean = interface(IViewValue<Boolean>)
    ['{13871F02-44C4-40C4-B2D7-4A592D2A86D3}']
  end;

  IViewValueString = interface(IViewValue<String>)
    ['{1322615D-4628-4398-806D-E4E34BCF3BCE}']
  end;

  IViewValueInteger = interface(IViewValue<Integer>)
    ['{42524A7C-8BAF-41B4-90D0-77D8C457EDC9}']
  end;

  IViewValueDouble = interface(IViewValue<Double>)
    ['{016EE305-1856-4CD5-ACC2-9021F52DED63}']
  end;
  ...
Die implementierenden Klassen sind dann analog dazu.


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:23 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