![]() |
Zyklusproblem ( Circular reference ) [solved]
Moi.
Ich hatte das Wochenende vorgehabt Programm schön OO zu machen, was fehlgeschlagen ist. Genauer gesagt, Circular Reference ist mein Problem. Verschiebend er Unit in die implementation funktioniert nicht, da ich die Komponenten im interface verwende. Folgendes Szenario (vereinfacht) Unit1 ist meine Form1. Inut2 enthält dann das eigentliche Programm (in wirklichkeit teilt die sich noch weiter auf ) Jedenfalls empfängt Unit1 die Ereignisse von der Form ( zB ButtonKlick ) und soll an meine Unit2 weiterleiten. Diese wertet diese aus ( eigentlich weiterleiten an weitere Klassen ) und reagiert halt drauf. In manchen Fällen brauch ich aber Zugriff auf die Form1. Bei mir ist es so, dass die Unit2 eine Instanz der Unit2 erstellt. Ich wollte beim Create noch als Parameter die Form1 übergeben. Um diese aber in unit2 abspeichern zu können brauch ich da die Unit1 im Interface. Generell heißt es, Crosslinks ( gegenseitige verlinkung ) sei ein ganz schlechter Programmierstill. Was lässt sich da machen? Denn selbst wenn ich Pointer der wichtigsten Komponenten in record packe und die schicke, sozusagen eine Kopie der Form1 erstelle, hilft mir das nicht weiter. Eine der Funktionen steuert die Sichtbarkeit der Form1 ( Form1.Hide und Form1.Show ). Ich wüsste nicht, wie ich das anstellen sollte. Einzigster Geistesblitz kam mir gerade eben beim schreiben hier. Ich hätte evtl die einzigste Chance, wenn ich in Unit1 die uses in implementationsabschnitt verschiebe. Jedenfalls erinnere ich mich da eine keine gekoppelten Funktionen im Interface, die nicht in implementation überleben würden. Andere Alternative wäre es prozeduren auf function umzustellen und in der Form1 dann den Rückgabewert auszuwerten. Glaub, in dem Fall würde die abstraktion mich aber meinem Ziel nicht weiterbringen, den Quellcode übersichtlicher zu gestallten. |
Re: Zyklusproblem ( Circular reference )
Ich löse das immer so: Wenn möglich, verschiebe ich die Uniteinbindung in den implementation-Abschnitt. Wenn das nicht geht, weil ich Typendefinitionen der anderen Unit brauche, schmeiße ich einfach alles in eine Unit.
|
Re: Zyklusproblem ( Circular reference )
Danke...
Also ist die einzigste Lösung das ganze in soweit zu verändern, dass man in einer Unit den Uses Teil auch in den implementation Bereich verschieben kann? Was die extra Unit betrifft, in die man alles reintut. Im Grunde ist das meine Unit2. Sie ist das Bindeglied zwischen der Form und den eigentlichen Funktionen. Wenn du mit alles in eine unit meintest, ich solle es in die Unit1 packen, wo die Form wäre, dann wär ich da, wo ich gestartet bin: 1500 Zeilen in einer Unit. Finde, dass das keinesfalls ein schöner Still sein kann. |
Re: Zyklusproblem ( Circular reference )
Zitat:
folgendes Unit-Modell vermeidet einige Fallstricke von vornherein: 1. es gibt i.A. eine Main-Unit mit der Hauptform der Anwendung. 2. Funktionen wie Einlesen einer Datei, Bearbeiten usw. werden zumindest ab einem bestimmten Umfang ausgelagert in eigene Units, besonders wenn sie eine eigene Form haben. Diese Units werden in der Main-Unit als uses meist im Interface deklariert, da sie von dieser ja benutzt werden (von wem sonst); manche brauchen auch Funktionen aus der Main-Unit, dann wird diese als uses im Implementationsteil der Sub-Unit deklariert. 3. Datentypen für das Programm werden oft an vielen Stellen benutzt und sind daher Ursache für Zirkularreferenzen; diese lagere ich von vornherein aus in eine "general"-unit, was dazu noch den Vorteil hat, dass alle solche Typdefinitionen an einer Stelle versammelt sind. Diese Unit ist überall in Interface als uses deklariert. Zweckmässigerweise werden hier auch generell benötigte Funktionen untergebracht wie etwa Umwandlungen zwischen den Typen oder eine allgemeine Status-Ausgabe; diese wird zwar auf die Main-Unit zugreifen müssen, aber im Implementation-Teil, macht also nix. Datentypen können durchaus auch Klassen mit Methoden sein - eine Klasse, die wirklich nur einen Datentyp implementiert, hat keinen logischen Grund auf die MainForm zuzugreifen (zumindest nicht im Interface, und auch in Implementation ist es schlecht programmiert). Mir ist bisher kein Fall unterlaufen, auch nicht bei mehr als 100 000 Zeilen Source, der sich damit nicht widerspruchsfrei aufbauen liess. Gruss Reinhard |
Re: Zyklusproblem ( Circular reference )
Reinhards Vorschläge sind erstmal grundsätzlich korrekt, bis auf Kleinigkeiten:
Zitat:
Delphi-Quellcode:
Ist nicht korrekt, aber
Unit A;
Interface Uses UnitB; // deklariert TTypeB, die aber im Interfaceabschnitt gar nicht verwendet wird ... Implementation Procedure Foo; Var b : TTypeB; // ... sondern erst hier ...
Delphi-Quellcode:
ist perfekt.
Unit A;
Interface Implementation Uses UnitB; // Hier gehörts hin. Procedure Foo; Var b : TTypeB; ... Grundsätzlich gilt; Deklarationsreferenzen (Uses, Var) sollten so nah wie möglich an der ersten Verwendung verschoben werden. C# und Java z.B. gehen ja bezüglich der Variablen so weit, das man die erst unmittelbar in dem Codeblock deklariert, in dem sie verwendet wird; Delphi leider nicht. Wenn nun einmal eine Unit A im Interface-Abschnitt etwas aus Unit B benötigt, und umgekehrt, dann kann man das in eine dritte Unit C auslagern. Das ist, was Reinhard im Punkt 3. ansprichst. Hier mal ein Beispiel;
Delphi-Quellcode:
Eine zirkuläre Referenz, die sich durch o.g. Regeln nicht auflösen lässt.
Unit UnitA;
Interface Uses UnitB; Type TTypeA = Class fB : TTypeB End; .... Unit UnitB; Interface Uses UnitA; Type TTypeB = Class fA : TTypeA; End; Hier kannst Du z.B. die Deklaration von TTypeA und TTypeB in eine separate Unit auslagern (Reinhards Vorschlag). Wenn nun aber die Units eigentlich nur diese eine Klasse deklarieren und implementieren, würde das natürlich den Konflikt auflösen, wäre aber u.U. bezüglich der Übersichtlichkeit kontraproduktiv. Hier würde ich erstmal nachdenken, was diese zirkuläre Referenz bedeutet: Wenn Du keinen Designfehler gemacht hast, bedeutet das, das wir es hier mit zwei voneinander abhängigen Klassen zu tun haben, die dann aber auch wirklich ein eine Unit gehören: Die Übersichtlichkeit würde eigentlich erhöht, da 'zusammenwächst, was zusammengehört'. Wem das dann doch zu unübersichtlich wird, der deklariert z.B. abstrakte Vorfahren von A und B in einer Hilfsunit:
Delphi-Quellcode:
Unit A greift nun auf ABTypes zu und definiert 'fB' als 'TAbstractTypeB'. Zirkuläre Referenz aufglöst, Übersichtlichkeit und Dateistruktur erhalten.
Unit ABTypes;
Interface Type TAbstractTypeA = Class <Abstrakte Deklaration der Klasse> End; TAbstractTypeB = Class <Abstrakte Deklaration der Klasse> End; End.
Delphi-Quellcode:
Hier gibt es keine Vorschrift: Einzig und allein das 'Design' sowie das Datenstrukturlayout zählen.
Unit UnitA;
Interface Uses ABTypes; Type TTypeA = Class fB : TAbstractTypeB End; |
Re: Zyklusproblem ( Circular reference )
wow, danke Reinhard Kern und alzaimar für die detailreichen tipps!
Aber zB bei Tipp2 von Reinhard komm ich nicht ganz klar. Zitat:
Also muss ich aus der SubUnit irgendwie auf die Form1 zugreifen. Wenn ich aber beim erstellen der SubUnit als Parameter für Create noch Form1 übergebe, um drauf zugreifen zu können, muss ich in SubUnit die MainUnit im interface haben. Oder hab ich da einen Denkfehler? Gruss |
Re: Zyklusproblem ( Circular reference )
Was willst du überhaupt mit create erstellen? Und die konkreten Parameter eines Konstruktors werden sowieso erst in der implementation benötigt (=nur da werden Konstruktoren aufgerufen), daher musst du die form1unit auch erst in der implementation einbinden.
|
Re: Zyklusproblem ( Circular reference )
Hatte zwischwendurch viel JAVA programmiert ( zu viel wenn mich einer fragt ). Dachte den konstruktor so aufzubauen in der SubUnit:
Delphi-Quellcode:
Dachte in der Form abzuspeichern, von wo die SubUnit aufgerufen wird, damit ich hier zB fVater.left := 100; benutzen kann.
unit SubUnit;
interface uses Unit1; type tHelp = class private fvater : tForm1; public constructor Create(vater : tForm1) end; implementation constructor tHelp.Create(vater : tForm1); begin inherited Create; fVater := vater; end; end. *edit: [ CODE ] durch [ DELPHI ] ersetzt. |
Re: Zyklusproblem ( Circular reference )
Das ist aber absolut kein Problem. Als Parametertyp gibst du TForm an, denn das hat ja schon die meisten Eigenschaften. Wenn du auf etwas in TForm1 Deklariertes zugreifen willst, musst du einfach (Vater as TForm1).button1 schreiben und natürlich deine Unit in die Implementation-uses einbinden.
Wenn du aber nur eine Instanz von TForm1 hast, kannst du dir den Parameter schenken, du greifst in der implementation auf form1 zu. Hoffe geholfen zu haben Apollonius |
Re: Zyklusproblem ( Circular reference )
Zitat:
Und glaub, ich weis jetzt, was du meintest. In der Unit1. wird ja eine globale Variable Form1 deklariert. *auf Kopf klatsch* Dann reicht ja Unit1 in implementation aus und greif auf diese Variable zu. Dann brauch ich die nichtmal an meine SubUnits zu übergeben. Die SubUnits holen sich einfach die Form. genial. Einfach, aber genial. Danke, werds mal gleich testen. Hätt ich bloss letzte Woche gefragt, wär mein Wochenende gerettet. Gruss |
Re: Zyklusproblem ( Circular reference )
Hallo,
ich würd das eher so machen, dass entweder du der INI-Unit deine TForm-Instanz übergibt und die macht dann was damit. Viel besser wärs aber - imo - dass die form auf die ini-unit zugreift, die ini/settings-klasse erstellt und sich dann selbst manipuliert. Ich weiß nicht, warum das bis jetzt noch niemand erwähnt hat, aber wenn zwei Units aufeinander zugreifen müssen, hast du meistens irgendwo einen Designfehler bzw. "unsauberes" Design. |
Re: Zyklusproblem ( Circular reference )
Hi!
Zitat:
Wenn man tIniFiles einbindet, kommt man mit einer Funktion pro Lesevorgang aus. Also kann man dann gleich alles in der HauptUnit behalten. Das führt aber dazu, dass die HauptUnit ( die, mit der Form) sehr stark anwächst. Dass Zyklen ein Anzeichen für Designfehler ist, hab ich gelesen. Deshalb bin ich auch nach der Suche einer guten Alternative, um den Code effektiv auf mehrere Units aufzuteilen, besonders um die HauptUnit zu entlasten und halt gleichzeitig die Zyklen zu vermeiden. Hab gehofft, dass beides geht. Scheint mir aber eher so, dass man ohne kompromiss nicht weiterkommt. trotzdem Danke für die Hilfe :) |
Re: Zyklusproblem ( Circular reference )
dann halt so:
Delphi-Quellcode:
procedure LoadCommonFormSettings(Form: TForm; Filename: string);
begin ... end; |
Re: Zyklusproblem ( Circular reference )
Also entweder übergebe ich dabei einen record von anderen Komponenten ( zB Checkobox, Labels ) oder ich lande wieder beim Zyklus.
Unit1 braucht die HelpUnit bei den Uses, um die Funktion aufzurufen. Hier hat man die Wahl, ob man die Form1 sozusagen kopiert, und die Objekte auf der Form1 einzeln übergibt, oder sich entscheidet es sauberer zu gestallten und die Form1 übergibt. Ich hätte Form1 bevorzugt, weil so weniger redundante Angaben kommen. Aufwand wäre höher, um da eine neue Komponente zu ergänzen. |
Re: Zyklusproblem ( Circular reference )
Wenn du Funktionen aufrufst, dann brauchst du die unit nur in der implementation, da man Funktionen nur in der implementation aufrufen kannst.
Units brauchst du nur im interface-Teil in der uses, wenn du im Interface in einer anderen Unit deklarierte Typen verwendest. Für Funktionen nicht. Es würde die Beantwortung deiner Fragen erheblich vereinfachen, wenn du dich etwas deutlicher ausdrücken würdest. |
Re: Zyklusproblem ( Circular reference )
Also eigentlich hab ich da weitestgehend keine Fragen. Dein letzter Hinweis (@Apollonius) hat mich eigentlich schon auf die gewünschte Lösung gebracht.
Mir geht es jetzt darum, ob ein "Zyklus", der durch implementation entschärft wurde, als unschönes Design zählt. Oder spricht man vom Designfehler, wenn der Zyklus rein auf der Interface Ebene stattfindet? und wegen dem vorherigem Beitrag: In der LoadCommonFormSettings(Form: TForm; Filename: string) müsste ich ein (Form as tForm1) machen müssen. Dann brauch ich unter uses (implementation) die Unit1. Oder ich übergebe da alle Komponenten, die verändert werden können. Dann hätte ich aber viele Parameter und wäre dadurch fehleranfällig. So, ich füg mal beim Titel ein [solved] hinzu. gelöst als:
Delphi-Quellcode:
Das reicht mir jedenfalls erstmal und ich komm wieder gut weiter :)
unit SubUnit;
interface type tMyHelp = class private public constructor Create; procedure Einlesen(ini : TIniFile); end; implementation uses Unit1; constructor tMyHelp.Create(image : tMyImage); begin inherited Create; end; procedure tMyHelp.Einlesen(ini : TIniFiles); begin Form1.checkbox1.checked := ini.readBool('A','B', true); // Form1 ist Variable in Unit1. Die wird durch uses "importiert" end; Hatte das mit der Variable "Form1" vergessen gehabt. |
Re: Zyklusproblem ( Circular reference )
Zitat:
Hast du nicht in deinem ersten Beitrag was von OO Programmierung geschrieben :gruebel: Dann solltest du aber wissen, dass man nicht unbedingt auf Instanzen direkt zugreifen soll. Was, wenn du plötzlich 2 Forms vom Typ TForm1 offen hast. Zugegeben, das ist in dem Beispiel eher unwahrscheinlich. Aber TForm1 könnte ja auch eine andere Klasse sein, von der du unterschiedliche Instanzen erzeugst. In Unit2 (bzw. in TForm2 oder was auch immer) willst du auf Eigenschaften und Methoden von TForm1 zugreifen. Also übergib gefälligst eine Instanz von TForm1 im Konstruktor von TForm2. Ich hoff, du verstehst was ich schreibe. Und um deinem zirkulärem Problem aus dem Weg zu gehen, deklariere den constructor von TForm2 so:
Delphi-Quellcode:
unit SubUnit;
interface uses forms; type tHelp = class private fvater : tForm; public constructor Create(vater : TForm) end; implementation uses unit1 ; constructor tHelp.Create(vater : TForm); begin assert ( vater is TForm, 'Parameter Vater con tHel.Create muss vom Type TForm1 sein.') ; inherited Create; fVater := vater; // irgendwas mit fVater machen with fVater as TForm1 do begin // ... end ; end; end. |
Re: Zyklusproblem ( Circular reference )
Zitat:
Hab jetzt den Code angeschaut und ... ja, so würde das auch funktionieren. Mir wird da nur etwas unwohl, wenn ich ständig (vater as tForm1) benutzen muss. Aber es ist das, was ich von anfang an eigentlich erreichen wollte: Einen echten Verweis auf meine aufrufende Form1. Wenn man aber den vater nicht im interface sondern in implementation abspeichert, kann man es auch als tForm1 abspeichern, richtig? Zitat:
Die neue Version hat dadurch nen >positive unendlichkeit< grösseren OO Anteil. Auch wird nie vollstänige OOP angewandt. Einzig Gewichtung wird geändert. |
Re: Zyklusproblem ( Circular reference )
Zitat:
|
Re: Zyklusproblem ( Circular reference ) [solved]
stimmt.
Bin halt noch ned so gut Vertraut mit dem Thema OOProgrammierung. Aber: Jeder muss mal klein anfangen :) Die Frage ist, ob man von anfang an es lieber "richtig" macht, oder ob man sich auf ein Kompromiss einlässt und sich auf das beschränkt, was nötig ist. In meinem Fall brauch ich zB der Form1 nur eine einzige Instanz. Demnach würde mir die Variable Form1 aus der Unit1 vollkommen ausreichen. Werde mir jedenfalls dein Beispiel noch abspeichern und für später aufheben :) |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:42 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