Tuesday, May 4, 2010

Visitor Pattern

The visitor pattern is a behavioral object design pattern. The visitor pattern is used to simplify operations on groupings of related objects. These operations are performed by the visitor rather than by placing this code in the classes being visited. Since the operations are performed by the visitor rather than by the classes being visited, the operation code gets centralized in the visitor rather than being spread out across the grouping of objects, thus leading to code maintainability. The visitor pattern also avoids the use of the instanceof operator in order to perform calculations on similar classes.
In the visitor pattern, we have a Visitor interface that declares visit() methods for the various types of elements that can be visited. Concrete Visitors implement the Visitor interface's visit() methods. The visit() methods are the operations that should be performed by the visitor on an element being visited.
The related classes that will be visited implement an Element interface that declares an accept() method that takes a visitor as an argument. Concrete Elements implement the Element interface and implement the accept() method. In the accept() method, the visitor's visit() method is called with 'this', the current object of the Concrete Element type.
The elements to visit all implement the accept() method that takes a visitor as an argument. In this method, they call the visitor's visit() method with 'this'. As a result of this, an element takes a visitor and then the visitor performs its operation on the element.
Let's illustrate the visitor pattern with an example. First, we'll define a NumberVisitor interface. This interface declares three visit methods which take different types as arguments. Note that if we only wrote one visit method, we'd have to use the instanceof operator or a similar technique to handle the different element types. However, since we have separate visit methods, we don't need the instanceof operator, since each visit method handles a different type.

NumberVisitor.java

package com.cakes;

import java.util.List;

public interface NumberVisitor {

 public void visit(TwoElement twoElement);

 public void visit(ThreeElement threeElement);

 public void visit(List elementList);

}
All of the elements classes to be visited will implement the NumberElement interface. This interface has a single method that takes a NumberVisitor as an argument.

NumberElement.java

package com.cakes;

public interface NumberElement {

 public void accept(NumberVisitor visitor);

}
Let's create a TwoElement class that implements NumberElement. It has two int fields. Its accept() method calls the visitor's visit() method with 'this'. The operator to be performed on TwoElement is performed by the visitor.

TwoElement.java

package com.cakes;

public class TwoElement implements NumberElement {

 int a;
 int b;

 public TwoElement(int a, int b) {
  this.a = a;
  this.b = b;
 }

 @Override
 public void accept(NumberVisitor visitor) {
  visitor.visit(this);
 }

}
The ThreeElement class is similar to TwoElement, except that it has three int fields.

ThreeElement.java

package com.cakes;

public class ThreeElement implements NumberElement {

 int a;
 int b;
 int c;

 public ThreeElement(int a, int b, int c) {
  this.a = a;
  this.b = b;
  this.c = c;
 }

 @Override
 public void accept(NumberVisitor visitor) {
  visitor.visit(this);
 }

}
Now, let's create a visitor called SumVisitor that implements the NumberVisitor interface. For TwoElement and ThreeElement objects, this visitor will sum up the int fields. For a List of NumElements (ie, TwoElement and ThreeElement objects), this visitor will iterate over the elements and call their accept() methods. As a result of this, the visitor will perform visit operations on all the TwoElement and ThreeElement objects that make up the list, since the call to accept() in turn calls the visitor's visit methods for the TwoElement and ThreeElement objects. 

SumVisitor.java

package com.cakes; import java.util.List; public class SumVisitor implements NumberVisitor { @Override public void visit(TwoElement twoElement) { int sum = twoElement.a + twoElement.b; System.out.println(twoElement.a + "+" + twoElement.b + "=" + sum); } @Override public void visit(ThreeElement threeElement) { int sum = threeElement.a + threeElement.b + threeElement.c; System.out.println(threeElement.a + "+" + threeElement.b + "+" + threeElement.c + "=" + sum); } @Override public void visit(List elementList) { for (NumberElement ne : elementList) { ne.accept(this); } } } Here is another visitor, TotalSumVisitor. In addition to summing up the int fields and displaying the sum, this visitor will keep track of the total sums of all the elements that are visited.

TotalSumVisitor.java

package com.cakes; import java.util.List; public class TotalSumVisitor implements NumberVisitor { int totalSum = 0; @Override public void visit(TwoElement twoElement) { int sum = twoElement.a + twoElement.b; System.out.println("Adding " + twoElement.a + "+" + twoElement.b + "=" + sum + " to total"); totalSum += sum; } @Override public void visit(ThreeElement threeElement) { int sum = threeElement.a + threeElement.b + threeElement.c; System.out.println("Adding " + threeElement.a + "+" + threeElement.b + "+" + threeElement.c + "=" + sum + " to total"); totalSum += sum; } @Override public void visit(List elementList) { for (NumberElement ne : elementList) { ne.accept(this); } } public int getTotalSum() { return totalSum; } } Let's see the visitor pattern in action. The Demo class creates two TwoElement objects and one ThreeElement object. It creates a list of NumberElements and adds the TwoElement object and the ThreeElement object to the list. Next, we create a SumVisitor and we visit the list with the SumVisitor. After this, we create a TotalSumVisitor and visit the list with the TotalSumVisitor. We display the total sum via the call to TotalSumVisitor's getTotalSum() method.

Demo.java

package com.cakes; import java.util.ArrayList; import java.util.List; public class Demo { public static void main(String[] args) { TwoElement two1 = new TwoElement(3, 3); TwoElement two2 = new TwoElement(2, 7); ThreeElement three1 = new ThreeElement(3, 4, 5); List numberElements = new ArrayList(); numberElements.add(two1); numberElements.add(two2); numberElements.add(three1); System.out.println("Visiting element list with SumVisitor"); NumberVisitor sumVisitor = new SumVisitor(); sumVisitor.visit(numberElements); System.out.println("\nVisiting element list with TotalSumVisitor"); TotalSumVisitor totalSumVisitor = new TotalSumVisitor(); totalSumVisitor.visit(numberElements); System.out.println("Total sum:" + totalSumVisitor.getTotalSum()); } } The console output of executing Demo is shown here.

Console Output

Visiting element list with SumVisitor 3+3=6 2+7=9 3+4+5=12 Visiting element list with TotalSumVisitor Adding 3+3=6 to total Adding 2+7=9 to total Adding 3+4+5=12 to total Total sum:27 Notice that if we'd like to perform new operations on the grouping of elements, all we would need to do is write a new visitor class. We would not have to make any additions to the existing element classes, since they provide the data but none of the code for the operations.

No comments:

Post a Comment