GofPatterns GofPatterns

Behavioral Patterns  «Prev 

Visitor Pattern Code

Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.

The Visitor pattern suggests that you place the new behavior into a separate class called visitor, instead of trying to integrate it into existing classes. The original object that had to perform the behavior is now passed to one of the visitor's methods as an argument, providing the method access to all necessary data contained within the object.
Now, what if that behavior can be executed over objects of different classes? For example, in our case with XML export, the actual implementation will probably be a little bit different across various node classes. Thus, the visitor class may define not one, but a set of methods, each of which could take arguments of different types, like this:
class ExportVisitor implements Visitor is
method doForCity(City c) { ... }
method doForIndustry(Industry f) { ... }
method doForSightSeeing(SightSeeing ss) { ... }
// ...

But how exactly would we call these methods, especially when dealing with the whole graph? These methods have different signatures, so we cannot use polymorphism. To pick a proper visitor method that is able to process a given object, we would need to check its class. Doesn’t this sound like a nightmare?
foreach (Node node in graph)
 if (node instanceof City)
  exportVisitor.doForCity((City) node)
   if (node instanceof Industry)
    exportVisitor.doForIndustry((Industry) node)
	 // ...

Question: Why don't we use method overloading? That is when you give all methods the same name, even if they support different sets of parameters. Unfortunately, even assuming that our programming language supports it at all (as Java and C#), it will not help us.
Since the exact class of a node object is unknown in advance, the overloading mechanism will not be able to determine the correct method to execute. It will default to the method that takes an object of the base Node class.
However, the Visitor pattern addresses this problem. It uses a technique called Double Dispatch, which helps to execute the proper method on an object without cumbersome conditionals. Instead of letting the client select a proper version of the method to call, how about we delegate this choice to objects we are passing to the visitor as an argument?
Since the objects know their own classes, they will be able to pick a proper method on the visitor less awkwardly. They "accept" a visitor and tell it what visiting method should be executed.
// Client code
2 foreach (Node node in graph)
3 node.accept(exportVisitor)
5 // City
6 class City is
7 method accept(Visitor v) is
8 v.doForCity(this)
9 // ...
11 // Industry
12 class Industry is
13 method accept(Visitor v) is
14 v.doForIndustry(this)
15 // ...

We had to change the node classes, but at least the change is trivial and it lets us add further behaviors without altering the code once again.
Now, if we extract a common interface for all visitors, all existing nodes can work with any visitor you introduce into the app. If you find yourself introducing a new behavior related to nodes, all you have to do is implement a new visitor class.