Das Visitor Pattern ist ein Verhaltensmuster, das es ermöglicht, neue Operationen auf Elementen eines Objekts zu definieren, ohne deren Klassenstruktur zu ändern. Es trennt die Operationen von den Objekten, auf denen sie angewendet werden. Dadurch bleibt der Code flexibel und erweiterbar, da neue Operationen hinzugefügt werden können, ohne bestehende Klassen zu modifizieren.
Was ist das Visitor Pattern?
Das Visitor Pattern besteht aus zwei Hauptkomponenten: dem Visitor und den Elementen. Der Visitor definiert die Operationen, die auf den Elementen ausgeführt werden. Jedes Element hat eine Methode, die den Visitor empfängt und ihm erlaubt, die entsprechende Operation auf sich auszuführen.
Im Wesentlichen erlaubt dieses Muster, dass ein Objekt ein „Besucher“-Objekt empfängt, das dann eine Operation auf diesem Objekt ausführt. Dies macht es einfach, neue Operationen hinzuzufügen, ohne die Klassen der Elemente selbst zu ändern.
Komponenten des Visitor Patterns
- Visitor: Ein Interface oder eine abstrakte Klasse, die die Methode
visit()
für jedes Element definiert. Diese Methode nimmt als Parameter ein Element und führt eine Operation auf diesem Element aus. - ConcreteVisitor: Eine konkrete Implementierung des Visitors. Diese Klasse definiert spezifische Operationen für jedes Element.
- Element: Eine abstrakte Klasse oder Schnittstelle, die eine
accept()
-Methode definiert. Diese Methode nimmt einen Visitor entgegen und lässt diesen die entsprechende Methode ausführen. - ConcreteElement: Eine konkrete Implementierung des Elements, die die
accept()
-Methode implementiert und den Besuch des Visitors erlaubt.
Beispiel des Visitor Patterns in C++
Im folgenden Beispiel wird das Visitor Pattern verwendet, um eine Operation auf einer Reihe von geometrischen Formen auszuführen, ohne deren Klassen zu ändern.
#include <iostream>
#include <vector>
// Forward declaration der Visitor-Klasse
class ShapeVisitor;
// Abstrakte Element-Klasse
class Shape {
public:
virtual void accept(ShapeVisitor& visitor) = 0;
virtual ~Shape() = default;
};
// Konkrete Element-Klassen: Kreis
class Circle : public Shape {
public:
void accept(ShapeVisitor& visitor) override;
};
// Konkrete Element-Klassen: Rechteck
class Rectangle : public Shape {
public:
void accept(ShapeVisitor& visitor) override;
};
// Abstrakte Visitor-Klasse
class ShapeVisitor {
public:
virtual void visit(Circle& circle) = 0;
virtual void visit(Rectangle& rectangle) = 0;
virtual ~ShapeVisitor() = default;
};
// Konkreter Visitor: Berechnung des Flächeninhalts
class AreaCalculator : public ShapeVisitor {
public:
void visit(Circle& circle) override {
std::cout << "Berechne die Fläche des Kreises" << std::endl;
}
void visit(Rectangle& rectangle) override {
std::cout << "Berechne die Fläche des Rechtecks" << std::endl;
}
};
// Konkreter Visitor: Berechnung des Umfangs
class PerimeterCalculator : public ShapeVisitor {
public:
void visit(Circle& circle) override {
std::cout << "Berechne den Umfang des Kreises" << std::endl;
}
void visit(Rectangle& rectangle) override {
std::cout << "Berechne den Umfang des Rechtecks" << std::endl;
}
};
// Implementierung der accept-Methode für Circle
void Circle::accept(ShapeVisitor& visitor) {
visitor.visit(*this);
}
// Implementierung der accept-Methode für Rectangle
void Rectangle::accept(ShapeVisitor& visitor) {
visitor.visit(*this);
}
// Client-Code
int main() {
// Erstellen von geometrischen Formen
Circle circle;
Rectangle rectangle;
// Erstellen des Visitors für die Flächenberechnung
AreaCalculator areaCalculator;
// Anwenden des Visitors auf die Formen
circle.accept(areaCalculator);
rectangle.accept(areaCalculator);
// Erstellen des Visitors für die Umfangsberechnung
PerimeterCalculator perimeterCalculator;
// Anwenden des Umfangs-Berechnungs-Visitors
circle.accept(perimeterCalculator);
rectangle.accept(perimeterCalculator);
return 0;
}
Erklärung des C++-Beispiels
- Shape (Abstrakte Element-Klasse): Die
Shape
-Klasse ist eine abstrakte Basisklasse für alle geometrischen Formen. Sie definiert die Methodeaccept()
, die einen Visitor entgegennimmt und dievisit()
-Methode des Visitors aufruft. - Circle und Rectangle (Konkrete Element-Klassen):
Circle
undRectangle
sind konkrete Klassen, die vonShape
erben. Sie implementieren dieaccept()
-Methode, die den jeweiligenvisit()
-Aufruf an den Visitor weitergibt. - ShapeVisitor (Abstrakte Visitor-Klasse): Die
ShapeVisitor
-Klasse definiert zweivisit()
-Methoden, die spezifisch für jedes Element sind. Diese Methoden müssen in den konkreten Visitors implementiert werden. - AreaCalculator und PerimeterCalculator (Konkrete Visitor-Klassen): Diese Klassen implementieren die
ShapeVisitor
-Schnittstelle und definieren die jeweiligen Operationen.AreaCalculator
berechnet die Fläche von Formen, währendPerimeterCalculator
den Umfang berechnet. - accept(): Die
accept()
-Methode in den Elementen stellt sicher, dass der passendevisit()
-Aufruf für das jeweilige Element erfolgt.
Vorteile des Visitor Patterns
- Erweiterbarkeit: Neue Operationen können durch das Hinzufügen neuer Visitor-Klassen leicht eingeführt werden. Es ist nicht nötig, die bestehenden Element-Klassen zu ändern.
- Zentralisierung der Logik: Die Operationen sind im Visitor zentralisiert, was den Code übersichtlicher macht. Es gibt keine Notwendigkeit, dieselbe Logik in mehreren Element-Klassen zu duplizieren.
- Trennung der Verantwortlichkeiten: Das Visitor Pattern trennt die Datenstruktur der Elemente von der Logik, die auf diesen Elementen ausgeführt wird. Dies sorgt für sauberen und wartbaren Code.
- Flexibilität: Das Muster ermöglicht es, die Operationen auf den Elementen zu ändern oder zu erweitern, ohne die Struktur der Element-Klassen zu beeinflussen.
Nachteile des Visitor Patterns
- Erhöhte Komplexität: Es können viele zusätzliche Klassen und Interfaces erforderlich sein, was den Code unnötig komplex machen kann, wenn nicht viele unterschiedliche Operationen benötigt werden.
- Verletzung des Open/Closed-Prinzips: Wenn neue Elemente eingeführt werden, muss der Visitor angepasst werden, um diese neuen Typen zu unterstützen. Dies kann zu Problemen führen, wenn viele verschiedene Element-Typen existieren.
- Enge Kopplung zwischen Elementen und Visitors: Das Element kennt den Visitor und ruft dessen Methoden auf. Dies führt zu einer stärkeren Kopplung zwischen den beiden, was in einigen Fällen unerwünscht sein kann.
Fazit
Das Visitor Pattern ist ein nützliches Designmuster, um neue Operationen auf einer Gruppe von Objekten durchzuführen, ohne deren Klassenstruktur zu ändern. Es bietet Flexibilität und Erweiterbarkeit, indem es die Logik vom Objekt trennt. Das Beispiel mit den geometrischen Formen zeigt, wie Besucher-Objekte zur Durchführung spezifischer Operationen auf den Elementen verwendet werden. Allerdings kann das Muster in Szenarien mit vielen unterschiedlichen Elementen oder Operationen zu einer erhöhten Komplexität führen.
Zur Übersicht Design-Pattern: Liste der Design-Pattern