DEV Community

Cover image for Visitor Pattern: The most widely misunderstood design pattern
Chamal Mallawaarachchi
Chamal Mallawaarachchi

Posted on

Visitor Pattern: The most widely misunderstood design pattern

Many thinks that this has something to do with tree like data structures.
Can't blame it because of the terms used in reference implementations like visit and accept. Though it is utilized mainly for tree structure related operations like interpeter designing, this pattern can be utilized in any other place where it's advantage seems profitable.

Instead of me trying to explain it using all the tech jargon, how about we try to understand it together using the problem it solves? Down? Let's go!

imagine that you have a parent class called coffee for a restaurent.

class coffee {
  ...
  void boilwater() { ... }
  ...
}
Enter fullscreen mode Exit fullscreen mode

there can be many methods that can be inherited from the coffee class to any child class. but there could also be some methods that cannot define inside the parent class. for an example, a method like nutritioncalculate() needs the coffee's information (properties) but the implenentation is differ from one child class to another.

now we have to go and add a method to all of the child coffee classes one by one. imagine having 100 child Coffee classes. plus nutrition calculation isn't actually a responsibilty of mocha or latte making unnessary methods tightly coupling to those classes violating the seperation of concerns.

visitor pattern to the rescue!

this is what visitor pattern is all about. It is a strategy based on Object Orientation to provide methods to those kind of child classes without interfering with those classes.

visitor pattern implementation

  1. first we define an interface called coffeevisitor. this interface provides method signatures for visiting all the coffees. note that these visitors define those specific coffee types as their parameters.
interface coffeevisitor {
    void visitmocha(mocha coffee);
    void visitlatte(latte coffee);
    ...
}
Enter fullscreen mode Exit fullscreen mode
  1. then we go back to our child coffee classes and add an accept() method. this accept method accepts the implementations of coffeevisitor.
class mocha extends coffee {
    void accept(coffeevisitor visitor) { ... }
}

class latte extends coffee {
    void accept(coffeevisitor visitor) { ... }
}
Enter fullscreen mode Exit fullscreen mode
  1. now it is time for us to introduce our custom methods. we only have to do is to implement the coffeevisitor and provide the methods for each child class.
class nutritionvisitor implements coffeevisitor {

    public void visitmocha(mocha coffee) {
        displaynutrition(coffee.ingredients);
    }

    void displaynutrition(string[] ingredients) {
        // display algorithm
    }
}
Enter fullscreen mode Exit fullscreen mode

like this way, we can implement coffeevisitor for each new method we need. are we actually doing less work by this way? you won't believe until have to deal with hundreds of classes. most importantly we have decoupled our displayNutrition method from coffee classes. plus we don't need to pork into those classes whenever we need to add another method. we can just implement coffeevisitor and be done with that.

That's the gist of it. Now this becomes a bit more complex when we need to get objects or types returned. That is mostly dealing with generics. I'll leave that for you to try and figure out. Despite the difficulty of initial implementation, it becomes rewarding when the codebase gets more and more complex. But that's how most of the design patterns are.

References

  1. Design Patterns: Elements of Reusable Object Oriented Software.

Top comments (0)