Vererbung Funktionalität erweitern



  • Folgende Frage zum Design einer Anwendung:

    Ich habe eine Klassenhierarchie, bei der ich die Daten der einzelnen Objekte mithilfe einer Funktion (readAttributes) setze. Also in etwa so:

    class Base{
     public:
      virtual void readAttributes(Attriubtes* data){
       // setze Membervariablen von Base entsprechend der Werte in Data
      }
    }
    

    Wenn ich jetzt die Attribute für ein abgeleitetes Objekt erstellen möchte, müssen natürlich auch die Daten des übergeordneten Objekts gesetzt werden. D.h., die Abgeleitete Klasse macht folgendes:

    class Derived:public Base{
     public:
      virtual void readAttributes(Attriubtes* data){
       Base::readAttributes(data);
       // setze Membervariablen von Base entsprechend der Werte in Data
      }
    }
    

    Im Prinzip funktionier diese Implementierung, nur finde ich es etwas unschön, dass in jeder abgeleiteten Klasse explizit die Funktion der Basisklasse aufgerufen werden muss (da man dass auch recht einfach vergessen kann^^).

    Daher meine Frage:
    Gibt es vielleicht eine Möglichkeit, die "eleganter" zu lösen, insbesondere, da dieses Beispiel nicht die einzige Gruppe von Funktionen ist, bei denen ein derartiges Verhalten notwendig ist, sodass eine allgemeine Lösung recht hilfreich wäre.

    Im Prinzip ist es ja so, ich habe die Funktion als virtuelle Funktion, was ja heißt, dass die abgeleitete Klasse eine völlig neue Funktionalität zur Verfügung stellen kann. Was ich aber tatsächlich möchte, ist, dass die abgeleitete Klasse die Funktionalität der Basisklasse erweitert. Ausspalten in zwei Methoden (eine virtuelle, protected und eine nicht virtuelle public) ist aufgrund der Tiefe der Klassenhierarchie auch nicht sinnvoll (bis 5 ebenen).



  • class Base
    {
        public:
            virtual ~Base()
            {
            }
    
            void readAttributes(Attributes* data)
            {
                // erledige Base-Aufgaben
                readAttributeImpl();
            }
    
        private:
            virtual void readAttributeImpl(Attributes* data) = 0;
    };
    
    class Derived : public Base
    {
        private:
            virtual void readAttributeImpl(Attributes* data)
            {
                // erledige Derived-Aufgaben
            }
    };
    


  • Mir ist keine andere Lösung bekannt.. Du musst den Methodenaufruf in jedem Fall an irgendeiner Stelle machen.

    Bei 5 Hirarchie Ebenen und Aufrufen von Funktionen aus der Basis Klasse würde ich mir aber an deiner Stelle mal überlegen ob das Design Konzept stimmt.

    Außerdem: wenn du die Methode aus der Basis Klasse jedes mal aufrufst, liegt meiner Meinung nach der Design Fehler schon darin das eine Methode die dauernd benutzt wird trotzdem abgeleitet wird. Ich würde die Methode dann eher protected machen in der Basis Klasse und eine Extra Methode virtual...=0; zusätzlich einbauen.

    #edit: Ok, Nexus war schneller und hat sogar eine Lösung. Über dein Design solltest du meiner Meinung nach trotzdem mal nachdenken, 5 Ebenen sind meiner Meinung nach sehr viel.



  • Das ist eine der Gründe, warum man virtuelle Funktktionen nicht als Schnittstelle anbieten sollte. Schau dir mal das NVI Idiom an.

    Du kommst so aber nicht um Mehrfache Funktionen nicht rum. Allerdings ist dann die Basisklasse für das Aufrufen verantwortlich und nicht die abgeleitete.



  • Danke erstmal für die Antworten.

    Allerdings haben die mich nicht so sehr weitergebracht, auch wenn ich mir NVI mal angeschaut habe.

    Vielleicht habe ich das Beispiel zu sehr reduziert...

    Für alle Objekte im Programm, deren Daten aus einer Datei eingelesen bzw. in eine Datei geschrieben werden sollen, gibt es eine abstrakte Basisklasse:

    class IAttributesObject{
     public:
      virtual void readAttributes(const Attributes* data) = 0;
      virtual void writeAttributes(Attributes* data) const = 0;
    }
    

    Dann gibt es z.B. die Objekte einer Szene, die z.B. alle durch Position, Rotation u. Skalierung. Davon abgeleitet gibt es z.B. Klassen für Lichtquellen, Darstellbare Objekte, .... Davon abgeleitet gibt es dann erst die konkrete Implementierung. Auf diese Art und Weise kommt dann schon eine recht große Hierarchietiefe zustande, die aber wohl schwierig vermeidbar ist. (ich bin mir jetzt nicht ganz sicher ob es auch irgnedwo 5 ebenen sind, aber die meisten implementierungen sind auf der 4. Ebene)

    Die Methode von Nexus scheint mir daher auch ideal, da in jeder Ebene weitere Daten hinzukommen, sodass ich diese Funktionen in jeder Ebene erneuern müsste, was dann auch kein Vorteil wäre.

    Ich hätte halt gedacht dass man sowas vielleicht mit templates lösen könnte. Zumindest theoretisch könnte man es ja folgendermaßen machen:

    class IAttributesObject{
     public:
      virtual void readAttributes(const Attributes* data) = 0;
      virtual void writeAttributes(Attributes* data) const = 0;
     protected:
      virtual void doReadAttributes(const Attributes* data) = 0;
      virtual void doWriteAttributes(Attributes* data) const = 0;
    }
    

    Dann würde in jeder abgeleiteten Klasse die readAttributes Funktion so aussehen:

    Derived::readAttributes(const Attributes* data){
     Base::readAttributes(const Attributes* data);
     doReadAttributes();
    }
    

    , d.h. der Quelltext würde in jeder abgeleiteten Klasse gleich aussehen (ein entsprechendes typedef für Base vorrausgesetzt).
    Kann man sowas mit templates irgendwie realisieren (mit Makros ginge es sicherlich, aber templates wären deutlich schöner).

    Eine Variante mit Templates, die ich ausprobiert habe, die auch funktioniert, basiert darauf, dass ich eine rekursive Templatefunktion habe, die für IAttributesObjekt partiell implementiert ist und für alle anderen Typen die eine doReadAttributes Funktion sowie sich selbst mit der Basisklasse als Templateparameter aufruft.
    Dieser Ansatz hat jedoch einige Nachteile.

    • Zum Einen muss der Typ, bei dem die rekursion begonnen wird, explizit als templateparameter angegeben werden.
    • Außerdem müssen die do... Funtkionen dann public oder die templatefunktion in jeder klasse als friend deklariert werden, was ich nicht so toll finde.

    Ich hoffe, damit konnte ich etwas klarer stellen, was ich brauche.



  • Die Methode von Nexus scheint mir daher auch ideal, da in jeder Ebene weitere Daten hinzukommen, sodass ich diese Funktionen in jeder Ebene erneuern müsste, was dann auch kein Vorteil wäre.

    Das ist genau das, was du mit dem NVI Idiom bekommst. 😉

    Finde ich auch am saubersten. Und da musst du keine Angst mehr haben, dass etwas vergessen geht, weil du das als Programmierer der Basisklasse in der Hand hast und du vergisst das da ja nicht. 😉



  • hmm, eig. meinte ich nicht ideal, weil es eben viel aufwand bedeuten würde, falls es irgendwie eine Lösung mit templates geben würde, wäre mir das schon lieber...

    für eine Hierarchieebene finde ich diese Lösung ganz gut, aber wenn es tiefer geht artet dass dann wohl doch ein bisschen aus

    ich hätte dann ja

    class Base{
     public:
      void readAttributes(const Attributes* att){
       // ... Basisklassenaufgaben
       readAttributesImpl(att);
      }
     protected:
      virtual void readAttributesImpl(const Attributes* att) = 0;
    };
    

    in der abgeleiteten Klasse htte ich dann

    class Derived1: public Base{
     protected:
      virtual void readAttributesImpl(const Attributes* att){
       // Aufgaben von Derived1
       readAttributesImplD1(att);
      }
      virtual void readAttributesImplD1(const Attributes*) = 0;
    }
    

    und dann eben in jeder Ebene so weiter...

    es garantiert auf jden Fall, dass man die Funktionsaufrufe nicht vergisst, da die ja schon in der Basisklasse stehen, aber man bekommt eben jede Ebene wieder ne neue Funktion dazu...

    Eben gerade deswegen würde was templateartiges mir gefallen, zu meinem Ansatz habe ich ja im letzen Post was geschrieben.



  • Hmm. Ich denke nicht, dass du da was mit templates einsparen könntest. Du willst ja auf jeder Ebene die Möglichkeit haben den Code zu ergänzen. Und templates ersetzen virtuelle Funktionen auch nicht, wenn du sie wirklich brauchst. Von dem her finde ich die Lösung so gar nicht schlecht.

    btw:
    Du kannst die zu überschreibenden Funktionen sogar private machen. Überschreiben kannst du sie trotzdem. Und aufrufen muss sie ja grundsätzlich nur die Basisklasse.



  • OK, wahrscheinlich ist das doch die beste Lösung. Aber dann habe ich nochmal ne Frage:

    Wenn ich eine Hierarchietiefe von drei hätte, wäre dass dann ja folgendermaßen:

    class Base{
     public:
      void f(){
       // gemeinsamer code
       //...
    
       // code, den die abgeleiteten Klassen zur Verfügung stellen
       f_impl();
      }
     private:
      virtual void f_impl() = 0;
    };
    
    class Der1: public Base{
     private:
      virtual void f_impl(){
       // gemeinsamer code für alle Der1 Objekte
       // ...
    
       // code, den die abgeleiteten Klassen zur Verfügung stellen
       f_impl_2();
    
       virtual void f_impl_2() = 0;
      }
    };
    
    class Der2: public Der1{
     private:
      virtual void f_impl_2(){
       // code für f_impl_2()
      }
    }
    

    Das Problem bei der ganzen Sache ist jetzt, dass Der2 zwar gezwungen ist, f_impl_2 zu definieren, jedoch nicht daran gehindert wird, die Implementierung von f_impl zu ändern.

    Gibt es eine Möglichkeit, abzusichern, dass eine Funktion nur in der Nächsttieferen Ableitungsebene geändert werden kann, bzw. dass sie nicht mehr geändert werden kann?



  • ngc92 schrieb:

    Gibt es eine Möglichkeit, abzusichern, dass eine Funktion nur in der Nächsttieferen Ableitungsebene geändert werden kann, bzw. dass sie nicht mehr geändert werden kann?

    Nein.


Anmelden zum Antworten