Designfrage



  • Hallo,

    mir ist klar, dass der Titel leider sehr inhaltslos gewählt ist. Allerdings weiß ich nicht, wie mein Problem in ganz kurzen Worten zu beschreiben ist:

    Ich habe eine (abstrakte) Basisklasse ABC , von welcher verschiedene Kindklassen erben. Die Instanzen der Kindklassen werden nun in einem std::vector<ABC*> gespeichert (bitte nicht auf "Speicherlöcher" etc. hinweisen. Das dient hier nur dem Verständnis).

    Jetzt möchte ich diese Liste durch verschiedene "Darsteller" D jagen, welche dann eine geeignete Repräsentation (z.B. als std::string ) aus dieser Liste erzeugen. D ist dabei wieder eine abstrakte Basisklasse.
    Das Problem ist nun: Wenn ich die Implementierung der Darstellung für ein Objekt vom Typ ABC in einer Subklasse von D implementiere, muss ich casten und prüfen, von welchem Typ das Objekt konkret ist:

    std::string create_representation(const std::vector<ABC*>& list) {
      std::stringstream str;
    
      for (size_t i = 0; i < list.size(); ++i) {
        if (dynamic_cast<ABCSubClass1*>(list[i]))
          str += do_representation(dynamic_cast<SubClass1*>(list[i]));
        else if (...)
          // ...
      }
      return str.str();
    }
    

    (Kurz hingeklatscht, läuft SO vllt. nicht, aber man bekommt die Idee)

    Das gefällt mir aber nicht:
    1. Man muss viel casten und prüfen, das ist m.M.n. schlecht (und langsam).
    2. Wird eine neue Subklasse von ABC dem Projekt hinzugefügt und kann in der Liste auftreten, so müssen ALLE Subklassen von D um diese neue Subklasse erweitert werden. Da passieren schnell Fehler.

    Ein alternativer Ansatz wäre es, die Darstellung für eine Subklasse von D direkt in der Subklasse von ABC zu implementieren:

    std::string represent_as_DSubClass1() {
      // ...
    }
    

    Da treten allerdings ähnliche Probleme auf:
    1. Wird eine neue Subklasse von D dem Projekt Hinzugefügt, muss ich in allen Subklassen von ABC eine Methode zur Repräsentation für die Subklasse hinzufügen.
    2. Die Repräsentation gehört plötzlich zur Subklasse von ABC . Das gehört da aber eigentlich gar nicht hinein!

    Meine Frage ist daher: Wie würde man dieses Problem am elegantesten lösen? Leider hängen Erweiterungen beider Klassen voneinander ab (so scheint es mir zumindest), daher sehe ich keine wirklich gute Lösung. Aber vielleicht hat ja jemand schon einmal mit dieser Problematik zu tun gehabt und weiß Rat.

    Grüße
    - Eulentick



  • Das sieht für mich wie nach einem Anwendungsfall für das Visitor-Pattern aus.



  • 314159265358979 schrieb:

    Das sieht für mich wie nach einem Anwendungsfall für das Visitor-Pattern aus.

    Das Visitor pattern hat er ja schon drin. Das Problem was er hat nennt sich double dispatch. ->Google hilft ggf. weiter.



  • Wie ich es verstanden habe ist es schon eine Möglichkei für Visitor.

    class Visitor;
    class ABC
    {
    public:
        virtual void Visit(const Visitor& v) const = 0;
    };
    
    class A;
    class B;
    
    class Visitor
    {
    public:
        void operator()(const ABC* abc) const
        {
            abc->Visit(*this);
        }
        virtual void VisitA(const A&) const = 0;
        virtual void VisitB(const B&) const = 0;
    };
    
    class A : public ABC
    {
    public:
        std::string s;
        A(const std::string& text) : s(text){}
        void Visit(const Visitor& v) const
        {
            v.VisitA(*this);
        }
    };
    
    class B : public ABC
    {
    public:
        std::string t;
        B(const std::string& text) : t(text){}
        void Visit(const Visitor& v) const
        {
            v.VisitB(*this);
        }
    };
    
    class Printer : public Visitor
    {
    public:
        void VisitA(const A& a) const
        {
            std::cout << a.s << "\n";
        }
    
        void VisitB(const B& b) const
        {
            std::cout << b.t << "\n";
        }
    };
    class ReversePrinter : public Visitor
    {
    public:
        void VisitA(const A& a) const
        {
            std::copy(a.s.rbegin(), a.s.rend(), 
                std::ostream_iterator<char>(std::cout));
            std::cout << "\n";
        }
    
        void VisitB(const B& b) const
        {
            std::copy(b.t.rbegin(), b.t.rend(), 
                std::ostream_iterator<char>(std::cout));
            std::cout << "\n";
        }
    };
    
    int main()
    {
        std::vector<ABC*> v;
        v.push_back(new A("hallo"));
        v.push_back(new B("welt"));
    
        Printer p;
        ReversePrinter pr;
    
        std::for_each(v.begin(), v.end(), p);
        std::for_each(v.begin(), v.end(), pr);
    
        return 0;
    }
    

    Allerdings ist das Visitor Muster nicht besonders beliebt, denn Du hast auch das Problem, dass Du alle Visitor anpassen musst, wenn neue ABC Untertypen dazukommen. Allerdings kannst Du das nciht einfach vergessen, wie in einer dynamic_cast Schleife, da die entsprechenden abstrakte Methoden implementiert werden müssen.

    Generell würde ich erstmal vom Visitor abraten. Kann man aber nur sagen, wenn man den genauen Anwendungsfall kennt. Wenn Du Die Darstellung in den Subklassen implementierst könnte es z.B. auch in Richtung Bridge gehen.


Anmelden zum Antworten