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 einemstd::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"
Djagen, welche dann eine geeignete Repräsentation (z.B. alsstd::string) aus dieser Liste erzeugen.Dist dabei wieder eine abstrakte Basisklasse.
Das Problem ist nun: Wenn ich die Implementierung der Darstellung für ein Objekt vom TypABCin einer Subklasse vonDimplementiere, 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 vonABCdem Projekt hinzugefügt und kann in der Liste auftreten, so müssen ALLE Subklassen vonDum diese neue Subklasse erweitert werden. Da passieren schnell Fehler.Ein alternativer Ansatz wäre es, die Darstellung für eine Subklasse von
Ddirekt in der Subklasse vonABCzu implementieren:std::string represent_as_DSubClass1() { // ... }Da treten allerdings ähnliche Probleme auf:
1. Wird eine neue Subklasse vonDdem Projekt Hinzugefügt, muss ich in allen Subklassen vonABCeine Methode zur Repräsentation für die Subklasse hinzufügen.
2. Die Repräsentation gehört plötzlich zur Subklasse vonABC. 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.