Your Guide to Design Patterns – Visitor Pattern (Part 2)

Design Patterns - The Visitor Pattern

Last week we started look at the Visitor pattern. This week we’ll continue where we left off last week, and write some more code for the Visitor pattern. And in so doing, we’ll end our exploration of design patterns.

Revision of the Visitor Pattern

Last week we said that the Visitor pattern allows us to separate algorithms from the objects on which they operate. By decoupling the algorithm from the data it works on, we can easily add future required behaviours to the class hierarchy without changing the classes.

The steps to define a new method include creating an interface for visitors, and adding accept() methods in the class hierarchy that a visitor will call. The accept() methods dispatch their calls back to the visitor using a double-dispatch mechanism. This allows the execution of a visit() method that applies to the specific class.

We use the Visitor pattern when we want to execute an operation on all of the elements of a complex structure, e.g, an object tree in a composite class hierarchy.

Visitor Pattern: Design Example

In our first person shooter game we’ve already created mechanised fighting units that we can command to move and fight. (See the previous posts on the Bridge, Command and Proxy patterns).

We already have a FightingUnit interface with methods for fighting and moving:

public interface FightingUnit {
   public void fight();
   public void turn(int degrees);
   public void advance();
   public void retreat();
}

We’ve already implemented the FightingUnit interface with concrete classes such as MechanisedFightingVehicles and MechanisedFightingRobots.

What happens if we need to add new functionality to all these implemented classes? We would have to change the FightingUnit interface and all the concrete classes. And every time we need to add extra functionality, we’d have to modify the same classes over and over again. Lots of additional coding and testing!

Visitor Pattern: Implementation

This would be a good place to use the Visitor pattern. We would still have to modify the existing FightingUnit interface and the previously written concrete classes, but we’d only do that once. After that, we would be able to add any number of extra operation by simply creating new visitors, as opposed to changing our existing code.

Remember that the Visitor pattern participants are the following:

  • Element: an interface that contains an accept() method that takes a Visitor as an argument.
  • ConcreteElement: implements the accept() method defined in Element.
  • Visitor: an interface that declares a visit() method for each class of ConcreteElement.
  • ConcreteVisitor: the concrete classes that appropriately implement each method declared by the Visitor interface.
  • The client code creates visitor objects and passes each of these objects to the accept() calls.

Let’s use the existing FightingUnit interface as the Element participant. We’ll add the accept(Visitor v) method to it:

public interface FightingUnit {
    public void fight();
    public void turn(int degrees);
    public void advance();
    public void retreat();
    public void accept(Visitor v); 
}

Next we will implement the accept() method in our MechanisedFightingVehicle and MechanisedFightingRobot classes. These would be the ConcreteElement implementations. Only one of the classes is shown; the other has the identical implementation of the accept() method.

public class MechanisedFightingRobot implements FightingUnit {

    public void fight() {
        System.out.println("Robot fighting!");
    }
    public void turn(int degrees) {
        System.out.println("Robot turning through " + degrees + " degrees");
    }
    public void advance() {
        System.out.println("Robot advancing...");
    }
    public void retreat() {
        System.out.println("Robot retreating...");
    }
    @Override
    public String toString() {
        return getClass().getName();
    }
    
public void accept(Visitor v) { v.visit(this); } }

Next we define the Visitor interface with a visit() method for each concrete class type:

public interface Visitor {
   void visit(MechanisedFightingVehicle vehicle);
   void visit(MechanisedFightingRobot robot);
}

Now we can create any number of concrete implementations of the Visitor interface that we need. Let’s implement a LogVisitor or a PersistenceVisitor for example.

public class LogVisitor implements Visitor {

    public void visit(MechanisedFightingVehicle vehicle) {
        System.out.println(vehicle + " is logged.");
        // some appropriate code to log a vehicle;
    }
    public void visit(MechanisedFightingRobot robot) {
        System.out.println(robot + " is logged.");
        // some appropriate code to log a robot;
    }
}

Finally we create the client code that creates visitor objects and passes each object to the accept() methods.

public class VisitorClient {

    public static void main(String args[]) {

        FightingUnit units[] = { new MechanisedFightingVehicle(), 
new MechanisedFightingRobot() }; Visitor logVisitor = new LogVisitor(); Visitor persistenceVisitor = new PersistenceVisitor(); for (FightingUnit unit : units) unit.accept(logVisitor); for (FightingUnit unit : units) unit.accept(persistenceVisitor); } }

We haven’t shown it here, but MechanisedFightingVehicles and MechanisedFightingRobots fit into a Composite pattern (see the relevant post on the Composite pattern). Some MechanisedFightingUnits would contain other MechanisedFightingUnits. A MechanisedFightingVehicle would contain MechanisedFightingRobots. We could have a variety of container classes such as MechanisedFightingVehicleMechanisedFightingVehicleTransporterMechanisedFightingBattleship, etc. The Visitor pattern works well with the Composite pattern.

JSE Examples of the Visitor Pattern

The Visitor pattern is recognizable by two different abstract/interface types which have methods that each take the other abstract/interface type. One calls the method of the other, and the other executes the desired strategy on it.

  • javax.lang.model.element.AnnotationValue and AnnotationValueVisitor.
  • javax.lang.model.element.Element and ElementVisitor.
  • javax.lang.model.type.TypeMirror and TypeVisitor.
  • java.nio.file.FileVisitor and SimpleFileVisitor.

Pros and Cons of the Visitor Pattern

The benefits of using the Visitor pattern include:

  • We can add functionality to class libraries for which we either do not have the source code or cannot change the source code.
  • We can get data from a disparate collection of unrelated classes, and use it to present the results of a global calculation to a client program.
  • We can group related operations into a single class rather than having to change or derive classes to add these operations.
  • We can collaborate with the Composite pattern.

The Visitor pattern works well when an operation is relevant to some of the classes, but not the others. We define the operation into a separate visitor class and implement only those visiting methods that accept the relevant object types. The rest of the methods will be empty stub methods.

The Visitor pattern is not good in situations where the visited classes aren’t stable. Every time a new composite hierarchy class is added, every visitor class must be modified.

Another disadvantage is that visitor classes might not have the necessary access to private members of the objects that they have to work with.

Conclusion

The Visitor pattern is often thought of as unnecessarily complex. This is due to the fact that a visitor can visit either a collection of different objects, a composite created by using the Composite pattern, or an inheritance tree.

We have to think about the problem carefully, and have a clear understanding of the problem before using Visitor, otherwise it can make our code unnecessarily complex. But in the right situation, the Visitor pattern can provide an elegant solution to complex problems.

I’m always interested in your opinion, so please leave a comment. Your feedback helps me write tips that help you.

Leave a Comment

Your email address will not be published. Required fields are marked *

Code like a Java Guru!

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.

Your Java tip is on its way!

Check that incusdata.com is an approved sender, so that your Java tips don’t land up in the spam folder.

Our privacy policy means your data is safe. You can unsubscribe from these tips at any time.