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() { ... }
...
}
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
- 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);
...
}
- then we go back to our child coffee classes and add an
accept()method. this accept method accepts the implementations ofcoffeevisitor.
class mocha extends coffee {
void accept(coffeevisitor visitor) { ... }
}
class latte extends coffee {
void accept(coffeevisitor visitor) { ... }
}
- now it is time for us to introduce our custom methods. we only have to do is to implement the
coffeevisitorand 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
}
}
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.
Top comments (0)