๐ถ Visitor
Visitor pattern allows you to seperate algorithmns from the objects on which they operate.
๐ Real-World Analogyโ
Imagine that you are on vacation and visiting various sights. You have compiled a list of places to visit. Depending on the type of place, you need to bring some necessities.
Letโs imagine we go to Korea:
- If itโs the Korean Demilitarized Zone, possibly some identification, such as a passport.
- If itโs Busan, probably some bathing suits and towels to enjoy the beaches.
- If itโs Jeju, possibly a set of good hiking boots to enjoy hiking at Hallasan.
๐ค Problemโ
Imagine, you are a member of a team that works on a vacation planning app. The app allows users to plan and book their vacation by selecting different cities, sightseeing locations, and attractions they would like to visit.
The app works by storing all vacation information in a single graph structure. Every node either represents a city or attraction. Each node is connected to other nodes if thereโs a road or path between them in the real world.
Your team has been tasked with adding the capability for users to export their vacation itinerary in JSON format. Initially, you planned to add an JSON export method to each of the node classes.
/**
* BAD EXAMPLE
*/
class Sight {
//...
public string GetJson() {
//...
}
}
class Beach {
//...
public string GetJson(){
//...
}
}
class Mountain {
//...
public string GetJson(){
//...
}
}
This meant that youโd have to modify each class to include the export method.
However, members of the project team raised concerns about this approach. They pointed out that the existing node classes had been developed and tested extensively, and they didnโt want to risk introducing bugs in the production system. Making modifications to these classes could pose a risk to the entire application.
They also raised concerns about the appropriateness of having the JSON export code within the node classes (POCOS). These classes were primarily designed to handle vacation point of interest data, and it could be confusing to have unrelated logic within them. It might also lead to future changes to the classes, which could be risky and further complicate the code.
Additional reasons for concern may be that, with alike requirements, usually other export data types are requested later. That would require to change these basic classes again.
๐ก Solutionโ
The Visitor Pattern allows you to place the new behavior in a separate class called Visitor instead of implementing logic directly into existing classess
/**
* GOOD EXAMPLE: ExportJsonVisitor and Client Implementation
*/
public class ExportJsonVisitor {
public string GetJson(Sight sight) {
//...
}
public string GetJson(Beach beach) {
//...
}
public string GetJson(Mountain mountain) {
//...
}
}
public class ExportXMLVisitor {
// ...
}
public class Client {
IEnumeration<IEntity> Entities; //list of all travel steps
ExportJsonVisitor ExportJsonVisitor = new ExportJsonVisitor();
ExportXMLVisitor ExportXMLVisitor = new ExportXMLVisitor();
public void Run() {
foreach(var entity in Entities) {
entity.GetExport(ExportJsonVisitor); //json impementation
entity.GetExport(ExportXMLVisitor); //xml impementation
}
}
}
To implement the Visitor Pattern, you would create a separate class ExportJsonVisitor, which would contain the desired behavior. You would then pass the object that needs to perform the behavior as an argument (passing this) to one of the visitorโs methods, giving the method access to all the data within the object.
public class Mountain : IVisitor {
public string GetExport(ExportXMLVisitor exportXMLVisitor) {
/**
* double dispatch => pointing back to visitor implementation
*/
exportXMLVisitor.GetXml(this);
}
public string GetExport(ExportJsonVisitor exportJsonVisitor) {
/**
* double dispatch => pointing back to visitor implementation
*/
exportJsonVisitor.GetJson(this);
}
}
However, in the case of different behavior across different classes, the visitor class would need to define a set of methods with different argument types. To ensure that the appropriate method is executed for each object, the Visitor pattern uses a technique called Double Dispatch.
Double Dispatch essentially delegates the choice of which method to execute to the object itself. When an object is passed to a visitor, the object โacceptsโ the visitor and selects the appropriate visiting method to be executed, based on its own class.
While implementing this pattern may require some minor changes to the node classes, it provides a more flexible and scalable solution in the long run. By creating a common interface for all visitors, any new behavior introduced can be executed across all existing nodes, and any new additions can be made simply by implementing a new visitor class.
๐๏ธ Structureโ
- Visitor interface that declares a set of visiting methods. These methods take concrete elements of an object structure as arguments. In programming languages that support method overloading, these methods can have the same name but must have different parameter types.
- Concrete Visitors are then created to implement several versions of each behavior, tailored for different concrete element classes. These visitors can be added to the program to introduce new behaviors to existing elements without modifying their classes.
- Element interface that declares a method for accepting visitors. This method takes in a parameter declared with the type of the Visitor interface. Each Concrete Element must implement the accept method, redirecting the call to the proper visitorโs method corresponding to the current element class. Itโs important to note that even if a base element class implements this method, all subclasses must still override it and call the appropriate method on the visitor object.
- Concrete element classes must implement the acceptance method. Clients work with objects from this collection through an abstract interface, allowing them to interface with any visitor thatโs introduced into the system.
- Client usually represents a collection or other complex objects, manages the Business Logic.
How to Implementโ
To implement the Visitor Design Pattern, you need to create two interfaces:
- one for visitors (IVisitor) and
- one for elements (IEntity)
The visitor interface has a set of methods, one for each element class in the program. The element interface adds an abstract method (GetJson(E: Sight)) to the base class of the element hierarchy. This method should accept a visitor object as an argument.
To use the Visitor pattern, you implement the acceptance methods in all element classes (GetJson(v IVisitor)). These methods redirect calls to a visiting method on the incoming visitor object, which matches the class of the current element.
The element classes should only interact with visitors through the visitor interface, while visitors must be aware of all element classes referenced as parameter types of the visiting methods.
You can create new visitor classes for any behavior that canโt be implemented inside the element hierarchy. If the visitor needs access to private members of the element class, you can either make those fields or methods public or nest the visitor class in the element class. This is only possible in some programming languages.
Finally, the client creates visitor objects and passes them into elements via GetJson methods to use the Visitor design pattern.
Pros & Consโ
โ Prosโ
โ Following Open/Closed Principle. New behavior can be introduced without changing how the underlying objects (classes) work.
โ Following Single Responsibility Principle. The logic can be versioned and held within the same Visitor Implementation.
โ Gathering of Information. The visitor implementation can gather useful information while working on the concrete object implementations. This is handy, when trying to traverse complex object structures, such as an object tree. The visitor can be applied to all objects of the tree.
โ Consโ
โ Maintainability. All visitors need to be updated each time a class gets added or removed to a element hierarchy.
โ Access. Visitors might not be able to access private fields and methods of objects they are intended to work on.
๐ Relation with other patternsโ
- Vistor pattern can be seen as another version of the Command pattern. Itโs objects can execute operations of various different classes.
- Visitor pattern can be used to Execute Operations over a complete complex structure.
- Combine Visitor and Iterator to traverse complex data structure and execute operations over itโs members, even if all of them are different classes.