- Visitor Pattern
-
Der Besucher (engl.: visitor bzw. visitor pattern) ist ein Entwurfsmuster aus dem Bereich der Softwareentwicklung und gehört zu der Kategorie der Verhaltensmuster (Behavioural Patterns). Es dient zum Kapseln von Operationen, die auf Elementen einer Objektstruktur ausgeführt werden. Neue Operationen können dadurch ohne Veränderung der betroffenen Elementklassen definiert werden. Das Muster ist eines der sogenannten GoF-Muster (siehe Viererbande).
Inhaltsverzeichnis
Verwendung
Die Integration verschiedener nicht miteinander verwandter Operationen in die Klassen einer Objektstruktur gestaltet sich oft schwierig. Bei der Erweiterung um neue Operationen müssen alle Klassen erweitert werden. Das Besuchermuster lagert die Operationen in externe Besucherklassen aus. Dazu müssen die zu besuchenden Klassen jedoch eine Schnittstelle zum Empfang eines Besuchers definieren.
Generell empfiehlt sich die Verwendung von Besuchern, wenn viele unterschiedliche, nicht verwandte Operationen auf einer Objektstruktur realisiert werden sollen, sich die Klassen der Objektstruktur nicht verändern, häufig neue Operationen auf der Objektstruktur integriert werden müssen oder ein Algorithmus über die Klassen einer Objektstruktur verteilt arbeitet, aber zentral verwaltet werden soll.
UML-Diagramm
Akteure
- Besucher
- definiert für jede Klasse konkreter Elemente eine Besuchsfunktion
- KonkreterBesucher
- implementiert Besuchsfunktionen
- jede Besuchsfunktion ist ein Fragment des Algorithmus, welcher auf der gesamten Objektstruktur angewendet wird
- lokaler Zustand dient als Kontext für den Algorithmus
- Element
- definiert eine Schnittstelle zum Empfang eines Besuchers
- KonkretesElement
- implementiert den Empfang eines Besuchers
- ObjektStruktur
- Kollektion oder zusammengesetzte Objektstruktur (siehe: Kompositum (Entwurfsmuster))
Vorteile
- Neue Operationen lassen sich leicht durch die Definition neuer Besucher hinzufügen.
- Verwandte Operationen werden im Besucher zentral verwaltet und von besucherfremden Operationen getrennt.
- Besucher können über mehreren Klassenhierarchien arbeiten.
Nachteile
- Die gute Erweiterungsmöglichkeit der Klassen von Besuchern muss mit einer schlechten Erweiterbarkeit der Klassen der konkreten Elemente erkauft werden. Müssen neue konkrete Elemente hinzugefügt werden, so führt dies dazu, dass viele Besucher-besuche-Methoden implementiert werden müssen.
- Neue Klassen konkreter Elemente erfordern Erweiterungen in allen Besuchern.
Beispiele
Virtuelles Reisebüro
Ein Reiseveranstalter bietet seinen Kunden verschiedene Busreisen, Ferienhäuser und Mietwagen an. Jedem Objekt sind eine Beschreibung und eine Preiskategorie für Sommer und Winter zugewiesen. Die Preise der Kategorien sind in einem Preismodul gespeichert. Bei Ferienhäusern sind darüber hinaus Bilder, bei Mietwagen technische Daten abgelegt. Sowohl die Klassen für Busreisen, Ferienhäuser und Mietwagen, als auch das Preismodul bieten eine Schnittstelle zum Empfang eines Besuchers. Das Preismodul ist außerhalb der Klassenhierarchie von Busreisen, Ferienhäusern und Mietwagen.
Ein Kunde kann sich nun eine Reise zusammenstellen. Fragt er dann nach dem Gesamtpreis, so besucht ein Besucher zunächst die interessierenden Objekte, fragt die jeweilige Kategorie ab. Für jede Kategorie verwaltet er einen lokalen Zähler. Zuletzt besucht er das Preismodul und berechnet auf Grund der dort abgelegten Preise und seiner lokal gesammelten Informationen den Gesamtpreis.
Entscheidet sich der Kunde, die Reise zu buchen, kann ein anderer Besucher eine Reisebestätigung erstellen. Dazu besucht er wieder die den Kunden interessierenden Objekte und das Preismodul. Sein lokaler Zustand besteht aus einem Dokument, das er gemäß den Informationen der Objekte gestaltet. Bei allen Objekten listet er zunächst die Beschreibung und die Preiskategorie auf, bei Mietwagen zusätzlich die technischen Daten. Beim Besuch des Preismoduls ergänzt er dann die einzelnen Beschreibungen um die konkreten Preise.
Beide Besucher übergreifen Klassenhierarchien, da sie sowohl auf der Klassenhierarchie der buchbaren Reiseelemente als auch auf dem Preismodul arbeiten.
Besucher in Übersetzern (Compiler)
Im Compilerbau liegt nach der syntaktischen Analyse meist ein abstrakter Syntaxbaum vor. Ein solcher Baum lässt sich durch Klassen für die verschiedenen Elemente und Verwendung von Aggregationen gut als Objektstruktur beschreiben. Auf dieser Objektstruktur kann man nun einen allgemeinen Besucher definieren, der den Baum traversiert. Dazu werden bei der Implementierung der Besuchsfunktion für eine Elementklasse des Baums die aggregierten Elemente nacheinander besucht. Von diesem allgemeinen Besucher lassen sich nun verschiedene Besucher ableiten, die unterschiedliche Operationen auf dem abstrakten Syntaxbaum implementieren.
In einem Besucher lässt sich die semantische Analyse realisieren. Dazu besucht dieser die Elemente des Baums und erweitert die Symboltabelle um Informationen zu Typen von Variablen und Routinen oder überprüft Ausdrücke unter Einbeziehung der Symboltabelle, ob sie wohlgetypt sind. Je nach den Eigenschaften der Quellsprache muss die Sammlung von Informationen und die Typprüfung auch auf zwei Besucher verteilt werden.
Ein weiterer Besucher kann dann die Synthese des Zielcodes realisieren. Auch dieser besucht dazu die einzelnen Elemente und sammelt die Zielcodefragmente in seinem lokalen Zustand. Abhängig von der Klasse des besuchten Elements kann er dann bereits gesammelte Fragmente zu größeren kombinieren.
Weitere Besucher können Debuginformationen sammeln oder Codeoptimierungen auf Quellcodebasis durchführen. Alle Besucher können dabei auf die Besuchsfunktionen des allgemeinen Besuchers zurückgreifen, wenn ein Element ohne weitere Operationen nur traversiert werden soll. Auch der Zielcode kann zunächst wiederum in einer Baumstruktur erzeugt werden, um dann verschiedene Optimierungen in unterschiedlichen Besuchern zu realisieren.
Codebeispiele
Java
Das folgende Codebeispiel ist in der Programmiersprache Java verfasst:
interface Visitor { void visit(Wheel wheel); void visit(Engine engine); void visit(Body body); void visitCar(Car car); } interface CarElement{ public void accept(Visitor visitor); } class Wheel implements CarElement{ private String name; Wheel(String name) { this.name = name; } String getName() { return this.name; } public void accept(Visitor visitor) { visitor.visit(this); } } class Engine implements CarElement{ public void accept(Visitor visitor) { visitor.visit(this); } } class Body implements CarElement{ public void accept(Visitor visitor) { visitor.visit(this); } } class Car { CarElement[] elements; public CarElement [] getElements(){ return elements.clone(); } public Car() { this.elements = new CarElement[] { new Wheel("front left"), new Wheel("front right"), new Wheel("back left") , new Wheel("back right"), new Body(), new Engine()}; } } class PrintVisitor implements Visitor { public void visit(Wheel wheel) { System.out.println("Visiting "+ wheel.getName() + " wheel"); } public void visit(Engine engine) { System.out.println("Visiting engine"); } public void visit(Body body) { System.out.println("Visiting body"); } public void visitCar(Car car) { System.out.println("\nVisiting car"); for(CarElement element : car.getElements()) { element.accept(this); } System.out.println("Visited car"); } public void visit(CarElement carElement) { // TODO Auto-generated method stub } } class DoVisitor implements Visitor { public void visit(Wheel wheel) { System.out.println("Steering my wheel"); } public void visit(Engine engine) { System.out.println("Starting my engine"); } public void visit(Body body) { System.out.println("Moving my body"); } public void visitCar(Car car) { System.out.println("\nStarting my car"); for(CarElement carElement : car.getElements()) { carElement.accept(this); } System.out.println("Started car"); } } public class VisitorDemo { static public void main(String[] args){ Car car = new Car(); Visitor printVisitor = new PrintVisitor(); Visitor doVisitor = new DoVisitor(); printVisitor.visitCar(car); doVisitor.visitCar(car); } }
Man beachte, dass die accept-Methode tatsächlich in jeder Element-Klasse implementiert werden muss, damit der Compiler die richtige überladene visit-Methode auswählen kann.
Verwandte Entwurfsmuster
Erzeugungsmuster: Abstrakte Fabrik | Singleton | Builder | Fabrikmethode | Prototyp
Strukturmuster: Adapter | Brücke | Decorator | Facade | Flyweight | Kompositum | Stellvertreter
Verhaltensmuster: Observer | Visitor | Interpreter | Iterator | Kommando | Memento | Schablonenmethode | Strategie | Vermittler | Zustand | Zuständigkeitskette
(Klassenmuster sind kursiv dargestellt)
- Besucher
Wikimedia Foundation.