Saturday, May 18, 2013

Desing Patterns in Java - Visitor

Visitor lets you define a new operation (method) on object without changing the classes (interface) of the elements on which it operates. This is the essence of visitor patter. The name "Visitor" is misleading and this pattern has noting to do with visiting, iteration or something like that. If you need to perform operations across a disparate set of objects, Visitor might be the pattern for you. Let see how does it work.

So let's presume we have have some disparate object structure and we want to run one operation on each object of this structure.
Here is how it works then:
The core of this pattern is Visitor interface. This interface defines a visit operation for each type in the object structure. The object (one object from structure) interface simply defines an accept method to allow the visitor to run some action over that object. This operation is here to to allow the visitor access to the object.

Here we have example that will calculate average Per Seat Fuel Economy for different type of vehicles. Calculation is quite simple: consumption / number of seats. Each of vehicle is represented with simple POJO's. Each of this object have one accept method so we have access to object through unified interface.

Interfaces:
public interface Visitable {
    
    void accept(Visitor visitor);
    
}

public interface Visitor {
    
    void visit(Bike bike);
    void visit(Bus bus);
    void visit(Car car);
    
}
Simple POJO's.
public class Bike implements Visitable {
    
    private int consumption;
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    
    public Bike(int consumption) {
        this.consumption = consumption;
    }
    
    public int getConsumption() {
        return consumption;
    }  
            
}

public class Bus implements Visitable {
    
    private int numberOfSeats;
    private int litersConsumption;

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    
    public Bus(int numberOfSeats, int litersConsumption) {
        this.numberOfSeats = numberOfSeats;
        this.litersConsumption = litersConsumption;
    }
   
    public int getNumberOfSeats() {
        return numberOfSeats;
    }

    public int getLitersConsumption() {
        return litersConsumption;
    }   
    
}

public class Car implements Visitable {
    
    private int consumption;
    private int weight;
    private int seats;
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public Car(int consumption, int weight, int seats) {
        this.consumption = consumption;
        this.weight = weight;
        this.seats = seats;
    }    
    
    public int getConsumption() {
        return consumption;
    }
   
    public int getWeight() {
        return weight;
    }

    public int getSeats() {
        return seats;
    }
}
Although this objects don't know how to calculate average per seat consumption we will learn them how to do that without even touching them:
public class EfficiencyVisitor implements Visitor {
    
    private int efficiency;
    
    @Override
    public void visit(Bike bike) {
        efficiency += bike.getConsumption() / 2;
    }

    @Override
    public void visit(Bus bus) {
        efficiency += bus.getLitersConsumption() / bus.getNumberOfSeats();
    }

    @Override
    public void visit(Car car) {
        efficiency += car.getConsumption() / car.getSeats() ;
    }

    public int getEfficiency() {
        return efficiency;
    }     
    
}
Client:

public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        
        int averageConsumptionPerSeat = 0;
        
        List <Visitable> vehicles = new ArrayList<>();
        
        Car mazda = new Car(6, 1024, 5);
        vehicles.add(mazda);
        Car skoda = new Car(7, 1200, 4);
        vehicles.add(skoda);
        Bus merc = new Bus(60, 30);
        vehicles.add(merc);
        Bike suzuki = new Bike(4);
        vehicles.add(suzuki);
        
        EfficiencyVisitor efficiencyVisitor = new EfficiencyVisitor();
        
        for (Visitable vehicle : vehicles) {
            vehicle.accept(efficiencyVisitor);
            averageConsumptionPerSeat += efficiencyVisitor.getEfficiency();
        }
        
        System.out.println(averageConsumptionPerSeat / vehicles.size());
    }
}

Best thing about this is that we can easily add new vehicle types or change existing methods without changing interface or methods on "old" vehicle types.

The whole point of this pattern is to clean up your code. It allow you to separate  certain logic from the elements themselves, keeping your data classes simple.

No comments:

Post a Comment