Visitor Design Pattern
Apparently Amazon asks this question.
Learned in CS247 - Software Engineering Principles.
What problem does Visitor pattern solve?
“How do we write a method that depends on two polymorphic types?
Visitor is a behavioural design pattern that lets you separate algorithms from the objects on which they operate. Combining overloading with overriding.
AFTER we’ve established the visitor pattern, we can add more visitors simply by adding more subclasses under the visitor. This is what doesn’t require changes to the public interface.
Whereas, if you didn’t use visitor, then to get more functionality out of your class with the accept method (the AST hierarchy in the course notes) you could only do this by adding a pure virtual method to all classes in the AST hierarchy.
Resource: https://refactoring.guru/design-patterns/visitor
Example: Lecture 20 of CS247 Lectures
We might want different behaviours for each combination of these concrete classes.
We can override this in Rock
and Stick
, but we don’t know which enemy we’re hitting. Same problem exists in reverse if we implement it inside of Enemy
.
- Notice that dynamic casting can partially fix this issue, but not completely
Solution: Visitor Pattern - combine overloading and overriding.
beStruckBy
is a virtual function →\rightarrow→ either callingMonster::beStruckBy
orTurtle::beStruckBy
(runtime)- Call
w.strike
on theWeapon&
. Strike is virtual →\rightarrow→Rock::strike
orStick::strike
(runtime) - Are we calling
Rock::strike(Turtle&)
orRock::strike(Monster&)
? We know the type of this. If this is aTurtle*
, then use theTurtle
version. If this is aMonster*
, then use theMonster
version (compile time)
Example Compiler Design (Ross)
Another use of Visitor Pattern
Another use of the visitor pattern is add behavior and functionality to classes, without changing the classes themselves.
Consider the following compiler design example.
How to Implement
Steps
- Declare the visitor interface with a set of “visiting” methods, one per each concrete element class that exists in the program.
- Declare the element interface. If you’re working with an existing element class hierarchy, add the abstract “acceptance” method to the base class of the hierarchy. This method should accept a visitor object as an argument.
- Implement the acceptance methods in all concrete element classes. These methods must simply redirect the call to a visiting method on the incoming visitor object which matches the class of the current element.
- The element classes should only work with visitors via the visitor interface. Visitors, however, must be aware of all concrete element classes, referenced as parameter types of the visiting methods.
- For each behavior that can’t be implemented inside the element hierarchy, create a new concrete visitor class and implement all of the visiting methods. You might encounter a situation where the visitor will need access to some private members of the element class. In this case, you can either make these fields or methods public, violating the element’s encapsulation, or nest the visitor class in the element class. The latter is only possible if you’re lucky to work with a programming language that supports nested classes.
- The client must create visitor objects and pass them into elements via “acceptance” methods.