Visitor Design Pattern
Visitor design pattern is a behavioral design pattern. The prime purpose of the visitor pattern is to add extra behavior to a class without modifying it. When I say extra behavior, I mean adding an extra operation or method to a class, however, at design time. Let’s first try to understand what it is and where it is used.
GoF definition
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
Let’s say you a set of elements or items in your project. These items can be anything e.g. Products such as Books, Electronics, Home Appliances and Furniture, etc. Now suppose you own a webstore and you let customers buy these items on your portal. Premium customers get different discounts on different items whereas as Non-Premium gets lower discounts than Premium.
One way of implementing it would be to add a calculate discount function in concrete classes directly but this way solution would not be scalable. The reason is every time we need to change the discount calculation logic, we need to change the concrete classes. More or less, it would also violate single responsibility principle.
The recommended solution to the problem would be to design our system in such a way that all concrete classes should be able to “Accept” a visitor. The visitor is later responsible to calculate the discount. Visitor class is normally overloaded based on the different concrete classes in the project. The ideal way of achieving it is to declare an interface with methods overloaded with concrete classes and the return value as expected from the concrete class.
Mapping our problem to the solution
Let’s say a premium customer has checked out a lot of items in his shopping cart. Before he pays, he wants to see how much discount has he got on the total purchase. He clicks on total button and our web store calculates the discount. Our Webstore client takes all the items one by one from shopping cart and calls the “Accept” method on the concrete classes. The accept method, in turn, calls visit method on ShoppingCartVisitor class. The class has a function called “Visit” overloaded with concrete classes’ type.
The proposed solution is scalable. The new concrete products can be added without changing existing ones. ShoppingCartVisitor can overload “Visit” method once more with new type created without changing existing overloads.
Now that we have got the essence of the visitor design pattern, let’s define the different players in the pattern:
- Visitor (IProductListVisitor)
Declares a Visit operation for each class of ConcreteElement in the object structure. The operation's name and signature identifies the class that sends the Visit request to the visitor. That lets the visitor determine the concrete class of the element being visited. Then the visitor can access the elements directly through its particular interface.
- ConcreteVisitor (ShoppingCartVisitor)
Implements each operation declared by Visitor. Each operation implements a fragment of the algorithm defined for the corresponding class or object in the structure. ConcreteVisitor provides the context for the algorithm and stores its local state. This state often accumulates results during the traversal of the structure.
- Element (IProductElement)
Defines an Accept operation that takes a visitor as an argument.
- ConcreteElement (Electronic,Book)
Implements an Accept operation that takes a visitor as an argument.
- ObjectStructure (ShoppingCart)
- Can enumerate its elements.
- May provide a high-level interface to allow the visitor to visit its elements.
- May either be a Composite (pattern) or a collection such as a list.
Visitor pattern is often advisable to be used when many unique and unrelated operations need to be performed on objects without “
polluting” their classes with these operations. Visitors let you assemble operations by defining them in one class.