Besucher-Design-Muster in Java

1. Übersicht

In diesem Tutorial stellen wir eines der verhaltensbezogenen GoF-Entwurfsmuster vor - den Besucher.

Zuerst erklären wir den Zweck und das Problem, das es zu lösen versucht.

Als nächstes werfen wir einen Blick auf das UML-Diagramm des Besuchers und die Implementierung des praktischen Beispiels.

2. Besucher-Design-Muster

Der Zweck eines Besuchermusters besteht darin, eine neue Operation zu definieren, ohne die Änderungen an einer vorhandenen Objektstruktur vorzunehmen.

Stellen Sie sich vor, wir haben ein KompositObjekt, das aus Komponenten besteht. Die Struktur des Objekts ist festgelegt - wir können sie entweder nicht ändern oder wir planen nicht, der Struktur neue Elementtypen hinzuzufügen.

Wie können wir unserem Code neue Funktionen hinzufügen, ohne vorhandene Klassen zu ändern?

Das Besucherentwurfsmuster könnte eine Antwort sein. Einfach ausgedrückt müssen wir eine Funktion hinzufügen, die die Besucherklasse zu jedem Element der Struktur akzeptiert.

Auf diese Weise ermöglichen unsere Komponenten der Besucherimplementierung, sie zu „besuchen“ und alle erforderlichen Aktionen für dieses Element auszuführen.

Mit anderen Worten, wir extrahieren den Algorithmus, der auf die Objektstruktur angewendet wird, aus den Klassen.

Infolgedessen werden wir das Open / Closed-Prinzip gut nutzen, da wir den Code nicht ändern, aber dennoch in der Lage sein werden, die Funktionalität durch die Bereitstellung einer neuen Visitor- Implementierung zu erweitern.

3. UML-Diagramm

Im obigen UML-Diagramm haben wir zwei Implementierungshierarchien, spezialisierte Besucher und konkrete Elemente.

Zunächst verwendet der Client eine Visitor-Implementierung und wendet sie auf die Objektstruktur an. Das zusammengesetzte Objekt durchläuft seine Komponenten und wendet den Besucher auf jede von ihnen an.

Besonders relevant ist nun, dass konkrete Elemente (ConcreteElementA und ConcreteElementB) einen Besucher akzeptieren und ihm lediglich erlauben, ihn zu besuchen .

Schließlich ist diese Methode für alle Elemente in der Struktur gleich. Sie führt einen doppelten Versand durch, indem sie sich (über das Schlüsselwort this ) an die Besuchsmethode des Besuchers übergibt .

4. Implementierung

Unser Beispiel ist ein benutzerdefiniertes Dokumentobjekt , das aus konkreten JSON- und XML-Elementen besteht. Die Elemente haben eine gemeinsame abstrakte Oberklasse, das Element.

Die Document- Klasse:

public class Document extends Element { List elements = new ArrayList(); // ... @Override public void accept(Visitor v) { for (Element e : this.elements) { e.accept(v); } } }

Die Element- Klasse verfügt über eine abstrakte Methode, die die Besucherschnittstelle akzeptiert :

public abstract void accept(Visitor v);

Wenn Sie das neue Element erstellen und es als JsonElement bezeichnen , müssen Sie die Implementierung dieser Methode bereitstellen.

Aufgrund der Art des Besuchermusters ist die Implementierung jedoch dieselbe. In den meisten Fällen müssten wir den Code für das Boilerplate aus einem anderen, bereits vorhandenen Element kopieren und einfügen:

public class JsonElement extends Element { // ... public void accept(Visitor v) { v.visit(this); } }

Da unsere Elemente es jedem Besucher ermöglichen, sie zu besuchen, nehmen wir an, dass wir unsere Dokumentelemente verarbeiten möchten , jedoch jedes auf unterschiedliche Weise, abhängig von seinem Klassentyp.

Daher hat unser Besucher eine separate Methode für den angegebenen Typ:

public class ElementVisitor implements Visitor { @Override public void visit(XmlElement xe) { System.out.println( "processing an XML element with uuid: " + xe.uuid); } @Override public void visit(JsonElement je) { System.out.println( "processing a JSON element with uuid: " + je.uuid); } }

Hier implementiert unser konkreter Besucher zwei Methoden, entsprechend eine für jeden Elementtyp .

Dies gibt uns Zugang zu dem bestimmten Objekt der Struktur, an dem wir die erforderlichen Aktionen ausführen können.

5. Testen

Schauen wir uns zu Testzwecken die VisitorDemo- Klasse an:

public class VisitorDemo { public static void main(String[] args) { Visitor v = new ElementVisitor(); Document d = new Document(generateUuid()); d.elements.add(new JsonElement(generateUuid())); d.elements.add(new JsonElement(generateUuid())); d.elements.add(new XmlElement(generateUuid())); d.accept(v); } // ... }

Zuerst erstellen wir ein Element Besucher, den Algorithmus hält die wir unsere Elemente gelten.

Als nächstes richten wir unser Dokument mit den richtigen Komponenten ein und wenden den Besucher an, der von jedem Element einer Objektstruktur akzeptiert wird.

Die Ausgabe wäre wie folgt:

processing a JSON element with uuid: fdbc75d0-5067-49df-9567-239f38f01b04 processing a JSON element with uuid: 81e6c856-ddaf-43d5-aec5-8ef977d3745e processing an XML element with uuid: 091bfcb8-2c68-491a-9308-4ada2687e203

Es zeigt , dass Besucher jedes Element unserer Struktur besucht hat, je nach Element - Typ, es versendet die Verarbeitung geeignete Methode und konnte die Daten von jedem zugrunde liegenden Objekt abzurufen.

6. Nachteile

Da jedes Entwurfsmuster auch für den Besucher seine Nachteile hat, ist es aufgrund seiner Verwendung schwieriger, den Code zu verwalten, wenn der Objektstruktur neue Elemente hinzugefügt werden müssen.

For example, if we add new YamlElement, then we need to update all existing visitors with the new method desired for processing this element. Following this further, if we have ten or more concrete visitors, that might be cumbersome to update all of them.

Other than this, when using this pattern, the business logic related to one particular object gets spread over all visitor implementations.

7. Conclusion

The Visitor pattern is great to separate the algorithm from the classes on which it operates. Besides that, it makes adding new operation more easily, just by providing a new implementation of the Visitor.

Furthermore, we don't depend on components interfaces, and if they are different, that's fine, since we have a separate algorithm for processing per concrete element.

Darüber hinaus kann der Besucher möglicherweise Daten basierend auf dem Element, das er durchläuft, aggregieren.

Um eine speziellere Version des Besucherentwurfsmusters anzuzeigen, lesen Sie das Besuchermuster in Java NIO - die Verwendung des Musters im JDK.

Wie üblich ist der vollständige Code im Github-Projekt verfügbar.