The Coffee Machine

Java & Eclipse tips blog

  • Categories

  • Archives

  •  

    August 2008
    M T W T F S S
        Sep »
     123
    45678910
    11121314151617
    18192021222324
    25262728293031

The Visitor Pattern

Posted by ObLiB on August 25, 2008

Design PatternsFor my work I needed to browse a “tree” to retrieve some data and make operations with it. To manage this, I made some research and I found a design pattern that I didn’t know yet: The Visitor Pattern.

To spread a hierarchy of classes, normally you simply add methods which provide desired behavior. It can happen however that this behavior is not consistent with the logic of the existent object model. It is also possible that you do not have access to existent code. In such situations, it can be impossible to spread the behavior of hierarchy without changing his classes. The Visitor Pattern precisely allows the developer of a hierarchy to insert a support for cases where other developers would like to spread his behavior.

This design pattern allows an external class to reach the internal variables of other classes (going contrary to concepts of POO). This model is useful when they have a reasonable number of instances of a small number of classes and that we want to perform operations which implicate them all (or many of them).

If deporting operations contained in a class towards another one can seem bad for POO, there are good reasons to make it. Indeed, if these operations are identical for every class instead of duplicating this method it is preferable to put these operations in a visitor (operation centralization). The visitor will use then the internal data of every object to perform asked operation.

In practice, the Visitor Pattern is realized in the following way: every class that can be “visited” must make available a public method « accept » taking as argument an object of type “visitor”. The « accept » method will call the « visit » method of the visitor object with the visited object as argument. In that way, a visitor object will be able to know the reference of the visited object and call his public methods to acquire data necessary for the treatment to perform (counting, generation of report, etc).

Visitor Pattern - UML

Here is an example to make the whole thing much more understandable ;)

Let’s create a hierarchy: We will define a house in which we will put a list of house elements (living room, bathroom, bedroom, garden, garage…) and implements the visitor pattern in this hierarchy…

First, let’s define the visit able interface to make our house elements able to accept the visitor:

public interface IHouseElement {
public void accept(IVisitor visitor);
}

Then, let’s define the house and its elements:

public class Garden implements IHouseElement {
private int sq_ft;
private boolean swimmingPool;
public int getSq_ft() {
return sq_ft;
}
public boolean hasSwimmingPool() {
return swimmingPool;
}
public Garden(int sq_ft, boolean swimmingPool) {
this.sq_ft = sq_ft;
this.swimmingPool = swimmingPool;
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}

public class CarPark implements IHouseElement {
private int carNb;
public int getCarNb() {
return carNb;
}
public CarPark(int carNb) {
this.carNb = carNb;
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}

public class Room implements IHouseElement {
private String name;
private int sq_ft;
public String getName() {
return name;
}
public int getSq_ft() {
return sq_ft;
}
public Room(String name, int sq_ft) {
this.name = name;
this.sq_ft = sq_ft;
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}

public class Bedroom extends Room {
private int bedNb;
public int getBedNb() {
return bedNb;
}
public Bedroom(String name, int sq_ft, int bedNb) {
super(name, sq_ft);
this.bedNb = bedNb;
}
public void accept(IVisitor visitor) {
visitor.visit((Room) this);
visitor.visit(this);
}
}

public class Bathroom extends Room {
private int showerNb;
private int bathNb;
public int getShowerNb() {
return showerNb;
}
public int getBathNb() {
return bathNb;
}
public Bathroom(String name, int sq_ft, int showerNb, int bathNb) {
super(name, sq_ft);
this.showerNb = showerNb;
this.bathNb = bathNb;
}
public void accept(IVisitor visitor) {
visitor.visit((Room) this);
visitor.visit(this);
}
}

public class House {
private String name;
private IHouseElement[] houseElements;
public String getName() {
return name;
}
public IHouseElement[] getHouseElements() {
return houseElements;
}
public House() {
this.name = "My house";
this.houseElements = new IHouseElement[] {
new Room("Living room", 600),
new Bedroom("first bedroom", 300, 1),
new Bathroom("little bathroom", 100, 1, 0),
new Bedroom("second bedroom", 500, 1),
new Bathroom("big bathroom", 400, 1, 1),
new Garden(1200, true),
new CarPark(1)};
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}

Next we have to define the visitor interface and we’ll create two different possible visitor implementations.

public interface IVisitor {
public void visit(House house);
public void visit(Room room);
public void visit(Bathroom bathroom);
public void visit(Bedroom bedroom);
public void visit(Garden garden);
public void visit(CarPark carPark);
}

First implementation: Visit the house and list its elements

public class PrintVisitor implements IVisitor {
public void visit(House house) {
System.out.println("Visiting house: "+house.getName());
for(IHouseElement room : house.getHouseElements()) {
room.accept(this);
}
System.out.println("House visited...\n");
}
public void visit(Room room) {
System.out.println("Visiting room: "+room.getName()+" ("+room.getSq_ft()+" sq ft)");
}
public void visit(Bathroom bathroom) {
System.out.println("  - "+bathroom.getShowerNb()+" shower(s)");
System.out.println("  - "+bathroom.getBathNb()+" bath(s)");
}
public void visit(Bedroom bedroom) {
System.out.println("  - "+bedroom.getBedNb()+" bed(s)");
}
public void visit(Garden garden) {
System.out.println("Visiting Garden: ");
if(garden.hasSwimmingPool())
System.out.println("  - With a swimming pool");
}
public void visit(CarPark carPark) {

System.out.println("Visiting CarPark: ");
System.out.println("  - "+carPark.getCarNb()+" parking place(s)");
}
}

Second implementation: Visit the house to get its total surface

public class SurfaceVisitor implements IVisitor {
private int totalSqFt;
public SurfaceVisitor() {
this.totalSqFt = 0;
}
public void visit(House house) {
System.out.println("Visiting house: "+house.getName());
for(IHouseElement room : house.getHouseElements()) {
room.accept(this);
}
System.out.println("\nHouse total surface = "+totalSqFt+" sq ft\n");
}
public void visit(Room room) {
System.out.println("Visiting room: "+room.getName());
System.out.println(" – "+room.getSq_ft()+" = "+(this.totalSqFt += room.getSq_ft()));
}
public void visit(Bathroom bathroom) {
// nothing to do
}
public void visit(Bedroom bedroom) {
// nothing to do
}
public void visit(Garden garden) {
System.out.println("Visiting Garden: ");
System.out.println(" – "+garden.getSq_ft()+" = "+(this.totalSqFt += garden.getSq_ft()));
}
public void visit(CarPark carPark) {
// nothing to do
}
}

Finally, run a little test:

public class Test {
public static void main(String[] args) {
House house = new House();
IVisitor printVisitor = new PrintVisitor();
IVisitor surfaceVisitor = new SurfaceVisitor();
house.accept(printVisitor);
System.out.println("--------");
house.accept(surfaceVisitor);
}
}

Here is the output:

Visiting House: My house
Visiting Room: Living room (600 sq ft)
Visiting Room: first bedroom (300 sq ft)
  - 1 bed(s)
Visiting Room: little bathroom (100 sq ft)
  - 1 shower(s)
  - 0 bath(s)
Visiting Room: second bedroom (500 sq ft)
  - 1 bed(s)
Visiting Room: big bathroom (400 sq ft)
  - 1 shower(s)
  - 1 bath(s)
Visiting Garden
  - With a swimming pool
Visiting CarPark
  - 1 parking place(s)
House visited...

--------
Visiting House: My house
Visiting Room : Living room
– 0 + 600 = 600
Visiting Room : first bedroom
– 600 + 300 = 900
Visiting Room : little bathroom
– 900 + 100 = 1000
Visiting Room : second bedroom
– 1000 + 500 = 1500
Visiting Room : big bathroom
– 1500 + 400 = 1900
Visiting Garden
– 1900 + 1200 = 3100

House total surface = 3100 sq ft

This example show you the power and the interest of this pattern. But you’ve got to take care; Even if the visitor pattern is great, he has some limits: He is very dependent of the data structure.

Look at our house example: Let’s imagine that the Bedroom and Bathroom classes are not implemented in the IVisitor interface. The Bedroom and Bathroom instance would never been visited, so we wouldn’t be able to distinguish those two different type of Room. Of course you will say that to avoid this problem we just have to take care to include all the subclasses of our hierarchy in the visitor interface (like we do in the example). But if our data structure improves? For example if a new Room type is defined in the future, you won’t be able to visit it without redefining the IVisitor interface :) .

Another problem is the risks present in the data structure itself. If you develop a visitor without knowing the code of the visited hierarchy, you can’t be aware of everything concerning the original behavior of this hierarchy and the way it’s coding, so you can get trapped.

So keep in mind that the Visitor Pattern is much more powerful with the following conditions:
  • All the types of nodes are stable
  • A common change is the addition of new functions that apply to different nodes

I hope that now you are aware of what’s the Visitor Pattern ;)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>