June 18, 2009

Visiting the Family - An Advanced Application of the Visitor Pattern

If we want to apply the Visitor Pattern in a complex class hierarchy, the plain Visitor allows us only to visit those classes, which are leafs in the inheritance tree. In this article I'll show a fancy mechanism to overcome this limitation. Not yet bored? Then dive into my explanations.

This is the second post in a loosely scheduled series of articles about the Visitor Pattern. Please find the first one here.

One of those things we've learned last time is the simple fact, that the Visitor will only visit "leafs" in a complex class hierarchy. Otherwise the polymorphic type dispatching will not work. Well, but we are sometimes faced with situations, where this limitation is not acceptable. Let's look at some fancy example, to illustrate my explanations. And since we all spent our two years military service with the imperial guards, here's an obvious taxonomy of different star wars vehicle types. visitor_hierarchy Usually complex type systems consist of several levels of abstraction, like our example. Here we have three:

  1. Either everything is the same, namely a Vehicle
  2. or it is a Vehicle Class like StarShip or Walker,
  3. or it is a concrete Vehicle like, well ... the BWing, for instance.
I think you get the point. With our current knowledge we could only visit concrete vehicles. But what do we do if we are a fleet commander and want to command all of our units to start moving. And moving means StarShips start flying and Walker go walking, have a look:
        
    // StarShips...
    public abstract class StarShip extends Vehicle{

        public void fly(){
            System.out.println("Yeaahhh, I'm flying, Baby.");
        }
    }    
    
    // Walker
    public abstract class Walker extends Vehicle{

        public void walk(){
            System.out.println("I'm going for a walk.");
        }
    }    
As a fleet commander we know for sure that we can utilize the Visitor Pattern to command our Vehicle [] fleet = { new BWing(), new XWing(), new AtAt() }.

A fleet lieutenant would do it like this:

    // clumsy fleet lieutenant's way to command his units
    public class ClumsyVisitor implements Visitor{
        public void visit(BWing bwing) {
            bwing.fly();
        }

        public void visit(XWing xwing) {
            xwing.fly();
        }

        public void visit(AtAt atat) {
            atat.walk();
        }
    }    
But we would not have become commander if we would unnecessarily repeat ourselves. As you've already realized, this is code duplication since we don't operate on the correct level of abstraction. The proper way is calling methods on the Vehicle Classes, not on the concrete Vehicles. But again, how to we do that? Here's the solution:
    public abstract class AbstractVehicleClassVisitor
            implements Visitor {

        public abstract void visit(Walker walker);
        public abstract void visit(StarShip starShip);

        public void visit(BWing bwing) {
            visit((StarShip) bwing);
        }

        public void visit(XWing xwing) {
            visit((StarShip) xwing);
        }

        public void visit(AtAt atat) {
            visit((Walker) atat);
        }
    }    
Tata: We've just introduced the proper level of abstraction to our Visitor as well by deriving a new type from Visitor, the AbstractVehicleClassVisitor. Or to be more precise, we defined the missing visit(Walker) and visit(StarShip) methods and delegate the 'leaf visits' to these new abstract methods. Visiting Vehicle Classes is now done by extending the AbstractVehicleVisitor. Hence our fleet commander command simply looks like:
    // that's how we command!
    public class MoveVisitor 
    extends AbstractVehicleClassVisitor{

        public void visit(Walker walker) {
            walker.walk();
        }

        public void visit(StarShip starShip) {
            starShip.fly();
        }
    }  
Calling for the troops breaks down to
    Vehicle [] fleet = { new BWing(), new XWing(), new AtAt() };
    for(Vehicle unit : fleet) {
        unit.accept(new MoveVisitor());
    }
    
    /* Which would output
    Yeaahhh, I'm flying, Baby.
    Yeaahhh, I'm flying, Baby.
    I'm going for a walk.
    */
Yep, that's all. Easy, isn't?

No comments: