Announcement

Collapse
No announcement yet.

Forward Deklaration

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Forward Deklaration

    Habe ich zwei Klassen:

    1. TNode
    2. TNodeList

    enthält nun TNode selber eine TNodeList (man stelle sich das Konstrukt eines TreeViews vor), so scheint es unmöglich beide Klassen in zwei verschiedenen Units zu implementieren.

    type TNodeList = class; // Forward Declaration

    TNode = class(TObject)

    List : TNodeList;

    end;

    TNodeList = class(TObject) // hier folgt die Deklaration

    ...

    end;

    Die Deklaration/Implementierung von TNodeList MUß in ObjectPascal anscheinend in der selben Unit erfolgen. Besitzen beide Klassen nun noch eine Menge weiterer Felder und Methoden geht das auf Kosten der Übersicht.

    Kennt jemand ein Verfahren, wodurch eine Modularisierung doch möglich ist. Vielleicht mittels Compiler-Direktiven, Header-Dateien, etc ?

    In C++ ist dergleichen schließlich auch möglich...

  • #2
    Das obige Beispiel hat keine Probleme. Du meinst bestimmt, das TNodeList wiederum einen TNode enthaelt.<br>
    Ich weiss jetzt auch nicht ob sich das in zwei Units trennen laesst. Ich weiss aber, das die Idee falsch ist. Uebersicht hin oder her, zwei so aufeinander bezogene Klassen MUESSEN in eine Unit

    Comment


    • #3
      TNodeList gibt über die Default-Array-Property Item[i: integer] natürlich ein TNode zurück. Daher die Überkreuzung.

      WARUM sollten zwei funktional von einander abhängige Klassen unbedingt in EINER Unit stehen. Ist das eine Konvention ? Enspricht das GUTEM Programmierstil ? Ich halte es für einen Mangel ...

      Wie gesagt: C++ ermöglicht dergleichen Technik

      Comment


      • #4
        Ohne Tricks geht das AFAIK nicht.<p>
        Du könntest versuchen, über einer oder beide Klassen noch ein Interface oder eine abstrakte Basisklasse zu legen und die Verknüpfung Knoten <-> Liste über die laufen zu lassen. Irgendwie so ähnlich:
        <pre class="sourcecode"><code>
        <font color="#003399"><i>// ================================================== =======================</i></font>
        <font color="#003399"><i>// In Unit 1:</i></font>
        <b>type</b>
        TNodeList = <b>class</b>(TObject)
        <font color="#003399"><i>//...</i></font>
        <b>end</b>;
        <br>
        TNode = <b>class</b>(TObject)
        <font color="#003399"><i>//...</i></font>
        List: TNodeList;
        <font color="#003399"><i>//...</i></font>
        <b>end</b>;
        <font color="#003399"><i>// ================================================== =======================</i></font>
        <br>
        <br>
        <font color="#003399"><i>// ================================================== =======================</i></font>
        <font color="#003399"><i>// In Unit 2:</i></font>
        <b>type</b>
        TNodeList_Impl = <b>class</b>(TNodeList) <font color="#003399"><i>// hier folgt die Deklaration</i></font>
        <font color="#003399"><i>//...</i></font>
        <b>end</b>;
        <font color="#003399"><i>// ================================================== =======================</i></font>
        </code></pre>
        Ist aber IMHO zu aufwändig, wenn's dir nur darum geht, die Unit aufzuteilen.

        <p>Die ganz harte Methode wäre, "untypisiert" zu arbeiten, z.B.
        <pre class="sourcecode"><code>
        TNode = <b>class</b>(TObject)
        <font color="#003399"><i>//...</i></font>
        List: TObject;
        <font color="#003399"><i>//...</i></font>
        <b>end</b>;
        </code></pre>
        Dann musst du aber andauernd casten und die Typsicherheit ist flöten -- auch nicht schön. :-(
        <p>Uli

        Comment


        • #5
          Der Weg über eine Basisklasse ist eine Möglichkeit. Untypisiert ist inakzeptabel schon auf Grund des Mehraufwands des 'Castens'.

          Gibt es keine Möglichkeit über eine Beeinflussung des Compilers

          Comment


          • #6
            Nein, da Deine "Wünsche" im Grunde gegen das PASCAL Konzept verstösst. Genau dieses "Feature" verhindert Fehler die z.B. in C/C++ möglich sind.<br>

            Forward Deklarationen von Klassen sind nur im gleichen Type Deklarationsbereich möglich. d.h.

            <pre>

            type
            TNode = class;
            TNodeList = class;<br>

            TNode = class(TObject)
            private
            FParent: TNodeList;
            end;<br>

            TNodeList = class(TObject);
            private
            FList: array of TNode;
            end;<br>

            </pre>

            ist korrekt. Dagagen ist

            <pre>

            type
            TNode = class;
            TNodeList = class;<bR>

            const
            XYZ = 1;<br>

            type
            TNode = class(TObject);
            ...
            ...<br>

            </pre>

            inkorrekt. Somit kann man auch nicht "Unit"-übergreifen Klassen als forwars deklarieren, da jede Unit seine eigenen Type-Sektionen besitzt.<br>

            Die korrekte Lösung wurde schon angesprochen. Sprachsyndaktisch sind also abstrakte Klassen der korrekte Weg.<br>
            ABER, es gibt noch einen zweiten Weg, nämlich über Interfaces. Dadurch wird es sogar die vollständige Trennung von der Deklaration zur Implementation der Nodes möglich. Sprich jedes Object, sei es ein C++/VB/Delphi Object das entweder ein INode oder INodeList Interface implementiert wäre korrekt.<br>
            Der Overhead der Interfaces, verglichen zum Nutzen, ist minimal.

            Gruß Hagen

            PS: Delphi behandelt allozierte Interface Objecte über deren ._AddRef und ._Release Methode automatisch wie Garbage Collections. Gerade bei hierarischen Node Listen ist das enorm von Vorteil. Die Deallozierungen der Node Interfaces wird dann nämlich autom. durch Delphi erledigt. Sowas wie

            <pre>

            var
            N: TNode;
            begin
            N := TNode.Create;
            try
            N.DoSomething;
            finally
            N.Free;
            end;
            end;<br>

            fällt flach und wird zu:<br>

            var
            N: INode;
            begin
            N := AllocateNode;
            N.DoSomething;
            end;<br>

            </pre&gt

            Comment


            • #7
              Also DAS klingt ja wirklich nach einem interessanten Weg. Hättest du vielleicht noch einen kleinen Literatur-Hinweis ? Bzw. ein Stichwort, über daß am schnellsten Informationen zu finden sind (Interface ist sehr weit gegriffen...) ?

              Gruß zurück,
              Tobia

              Comment


              • #8
                Abgesehen von dem korrekten Weg den dir Hagen weist ist deine Vermutung das das Trennen in separate Units guter Stil sei einfach falsch. Das C++ es erlaubt ist kein Guetebeweis. C++ ist aus objektorientierter Sicht eher den Problemen als den Loesungen zuzuordnen

                Comment


                • #9
                  Bisher sehe ich hier nur verschiedene Meinungen. Nicht aber den konkreten Grund warum die Designer von Object-Pascal zuweilen eine Trennung verschiedener Klassen nicht ermöglichen. Die Units kamen, glaube ich, mit Turbo-Pascal. Eine guter Weg damals mittels Project-Tools seine Arbeit hierarchisch und übersichtlich ordnen zu können.
                  Warum also der Schritt zurück ?

                  Es muß ja nicht pro Unit eine Klasse sein. Aber 1000 Zeilen Code anstatt in einer Unit auf 5 à 200 Zeilen Code aufzuteilen gibt mir kontextbezogen sicherlich eine bessere Übersicht.

                  Also, ich will den Stil nicht bewerten, aber den Grund für diese Design-Restriktion wüßte ich schon ganz gerne ..

                  Comment


                  • #10
                    Welcher Schritt zurueck? Pascal ist nun mal nicht C++. Es gibt nun mal bestimmte Limitierungen in der Sprache und es sind andere Limitierungen als bei C++. Features einzufordern nur weil sie angenehm sind ist nutzlos. Man muss sich Gedanken machen was das fuer die Sprache bedeutet und ob sich der Aufwand lohnt den Compiler zu aendern. Das kann naemlich in ein komplettes Redesign der sprache ausarten

                    Comment


                    • #11
                      Der 'Rückschritt' ist die Einführung von Units -> Modularisierung, dann aber die Limitierung im Sprach-Konstrukt. Wie gesagt, mir liegt es fern, hier etwas zu bewerten. Ich will es nur nutzen. Und den Grund für die 'Limitierung' wüßte ich noch immer ganz gerne ..

                      Comment


                      • #12
                        Das Unit-Konzept selbst steht dir im Wege. Es muessten sich die Units wechselseitig ins uses aufnehmen, damit sie den Typ kennen der in der anderen Unit steht. Nun muss aber beim Uebersetzen der einen Unit die andere bereits uebersetzt sein, damit der Name bekannt ist. Es entsteht ein Deadlock.<br>
                        C loest das Problem indem es die Deklaration der Typen in Include-Files auslagert. Das lauft auf eine Schwaechung des Typkonzepts in C hinaus, da faktisch die Typdeklaration von TNode und TNodeList in beiden C files steht (includen ist textuelles Einsetzen). In Pascal waeren die beiden TNode Typen inkompatibel, da sie aus verschiedenen Units stammen und jede Unit ihren eigenen Namespace bildet

                        Comment


                        • #13
                          Ich bedanke mich, Robert. Das erhellt mir die Angelegenheit und zeigt mir ein tieferes Verständnis des Unit-Konzepts. Jetzt weiß ich zumindest in welcher Ecke es mir an Klarheit mangelt. Dem werde ich abhelfen...

                          Tobia

                          Comment


                          • #14
                            Hm, ich weiß nicht was ICH gelernt habe, aber die STRIKTE Modularisierung im PASCAL ist von N.Wirth von Anfang an so konzipiert wurde. Units->Interface+Implementation, Interface->Typen=Constanst=gloable vars, Typen=Objects->class->>end = private-protected-public-published, Impemletation=functions/procedures->begin end, try finally/except end.<br>

                            ALLES in PASCAL hat eine Begin-/und Enddeklaration also einen Scope=Gültigkeitsbereich. NIEMALS wird in PASCAL dieser Scope verletzt, selbst EXIT/GOTO und CONTINUE verletzen dieses Konzept nicht.<br>

                            Deine Vorstellungen wiedersprechen also PASCAL.<br>

                            So nun noch eine Lösung: Nutze Interfaces !!
                            Dazu benötigst Du EINE gemeinsamme Unit die NUR die Interfaces deklariert, also nichts in der Implementation Section.<br>

                            Dann wird eine weitere Implemntierende Unit erzeugt in der Du nur die Allokatoren als functionen Deklarierst. Im Interface werden keienrlei Objecte deklariert. Dagegen werden diese im Implementationsteil als lokale Klassen definiert die die Interfaces unterstützen. Dieses Klassen sollten von TInterfacedObject abgeleitet werden.<br>

                            Nachteil !! Interfaces haben keinerlei Scopes in deren Deklarationen, d.h. alles ist Public.<br>
                            Dies lässt sich umgehen indem man zusätzliche Interfaces deklariert die einen Zugriff auf "interne" Funktionen ermöglichen.<br>

                            z.B.

                            <pre>
                            type
                            INode = interface
                            {...} // GUID here
                            function Parent: INode;
                            function Count: Integer;
                            function GetChild(Index: Integer): INode; // kann nicht privat deklariert werden
                            property Child[Index: Integer]: INode read GetChild; default;
                            end;<bR>

                            INodeEnum = interface
                            function Count: Integer;
                            function GetNode(Index: Integer);
                            property Node[Index: Integer]: INode read GetNode; default;
                            end;

                            </pre>

                            So das wars schon zur Node. Eine TNodeList benötigen wir nicht das es NUR von der Implementierung des dahinterliegenden Objectes abhängt WIE das erledigt wird.<br>
                            So mit obigem INodeEnum kann ausgehend von einer Node ALLE Nodes samt deren Unternodes iteriert werden.

                            z.B.

                            <pre>

                            var
                            N: INode;
                            E: INodeEnum;
                            begin
                            N := AllocatedNodeTree;
                            // greift auf die Childrens von N zu
                            for I := 0 to N.Count -1 do
                            WriteLn( N[I].Count );
                            // greift auf die Childs und deren Childs usw. von N zu
                            E := N as INodeEnum;
                            for I := 0 to E.Count -1 do
                            WriteLn( E[I]. Count );
                            end;<br>

                            </pre>

                            Gruß Hage

                            Comment


                            • #15
                              Danke der Erläuterung, Hagen. Ich werde mich damit auseinanderstzen. Bis jetzt assoziere ich mit 'Interfaces' hauptsächlich COM-Objekte, die ich aber auch nur vom flüchtigem Studium her kenne..

                              Comment

                              Working...
                              X