Designfrage bzgl. Klassenhierarchie



  • Hiho,

    Ich habe in meinem Programm eine Klassenhierarchie, die sich vor allem eher lange ist (~15 Schichten) aber nicht sonderlich breit (1-3 abgeleitete Klassen pro Klasse). Die Klasseneinteilung an sich macht m.E.n. Sinn; jede Klasse fügt neue Member und Funktionen hinzu und das Verhalten der virtuellen Methoden ist auch sinnvoll.
    Meine Frage nun:
    Ich habe nun z.B. ganz zu oberst in der Hierarchie eine abstrakte Klasse, die eine virtuelle Funktion Refresh anbietet. Der Sinn dieser Methode ist es, gewisse Caches und Puffer zu aktualisieren und bestimmte Berechnungen neu durchzuführen. In meiner Hierarchie gibt es sowohl Klassen, die nichts zu machen haben im Falle eines Refresh s, als auch jene, die einen bestimmten Code ausführen müssen. Das Problem ist jedoch, dass der alte Code von Refresh überschrieben würde, wenn ich einfach die Methode implementiere tiefer in der Hierarchie.

    Mögliche Lösung:

    class Base {
    public:
    	virtual ~Base() = default;
    	virutal void Refresh() {}
    };
    class Foo : public Base {
    protected:
    	void RefreshFoo() {/*...*/}
    public:
    	virtual void Refresh() override
    	{
    		RefreshFoo();
    	}
    };
    class Bar : public Base {
    protected:
    	void RefreshBar()
    	{
    		RefreshFoo();
    		// ...
    	}
    public:
    	virtual void Refresh() override
    	{
    		RefreshBar();
    	}
    };
    class Qux : public Base {
    protected:
    	void RefreshQux()
    	{
    		RefreshBar();
    		// ...
    	}
    public:
    	virtual void Refresh() override
    	{
    		RefreshQux();
    	}
    };
    

    Das ist meine aktuelle Lösung. Zu den Probleme zählen, dass ich oft Base<...>:: schreiben muss, dass es sehr redundant ist und dass es mühsam zu ändern ist.

    Mir ist da auch noch in den Sinn gekommen, dass man Refresh non- virutal machen und dann in der Basisklasse einen Vektor mit Listener machen könnte. Dann würde jeder Konstruktor bedingt seine Methoden hinzufügen. Was mir daran gar nicht gefällt, ist, dass da dynamischer Speicher verwendet wird. Vor allem weil die Anzahl der Listener beim Instantiieren feststünde. Außerdem vermute ich stark, dass die Performance da nicht ungeschoren davonkommt...

    Kennt da jemand eine Lösung zu diesem Problem? Ist das ein wohlbekanntes Problem?

    LG



  • Ich weiß nicht, ob ich dein Problem verstehe. Ist das vielleicht eine Lösung?

    struct b {
      virtual void doit(){}
    };
    
    struct d : b{
      void doit() { // do something
         b::doit();
         // do an other thing
      }
    };
    


  • Nein, weil std::unique_ptr<b>{std::make_unique<d>()}->doit(); nur den b -spezifischen Code aufruft.
    Ich will, dass ich die Methode von der Basisklasse aus aufrufen kann und dass dann alle abgeleiteten "Schichten" ihren zusätzlichen Code ausführen. Beispielsweise:

    std::unique_ptr<Base>{std::make_unique<Foo>()}->Refresh(); // soll die Daten von Base und Foo refreshen
    std::unique_ptr<Base>{std::make_unique<Bar>()}->Refresh(); // soll die Daten von Base, Foo und Bar refreshen
    std::unique_ptr<Base>{std::make_unique<Qux>()}->Refresh(); // soll die Daten von Base, Foo, Bar und Qux refreshen
    


  • gezly schrieb:

    Nein, weil std::unique_ptr<b>{std::make_unique<d>()}->doit(); nur den b -spezifischen Code aufruft

    Nein, warum sollte es?



  • manni66 schrieb:

    Nein, warum sollte es?

    Ach, das wird auch dynamisch gebunden, obwohl kein virtual vor der Definition von doit in d da steht? Ist mir neu, sorry. Ich mach schon aus Routine überall virtual und override dran.

    Ja, das löst es. Das ist dann prinzipiell dasselbe wie mein Code im OP nur mit weniger Boilerplate aber mit den noch immer selben Probleme:
    - jedes mal die Basisklasse angeben zu müssen, insb. wenn Template-Argumente involviert sind
    - sehr mühsam zu ändern, wenn es Änderungen in der Klassenhierarchie gibt
    - keine Checks, falls ich den Basisklassen-Aufruf irgendwo vergesse



  • gezly schrieb:

    - jedes mal die Basisklasse angeben zu müssen, insb. wenn Template-Argumente involviert sind
    - sehr mühsam zu ändern, wenn es Änderungen in der Klassenhierarchie gibt
    - keine Checks, falls ich den Basisklassen-Aufruf irgendwo vergesse

    - naja, nur wenn die Funktion in einer Klasse auch implementiert wird. Das ist ja so wie du sagst nicht immer der Fall
    - wenn du die Schnittstelle dauernd änderst, ist die Klassenhierarchie vielleicht dein geringstes Problem
    - dann wirst du wohl dran denken und gut testen müssen

    Die 15 Ebenen sind doch schon nicht ganz einfach...


Anmelden zum Antworten