Sunday, January 8, 2017

Ditch Visitor Pattern for A Better Solution

You could just ditch Visitor Pattern as there is a better solution for Java.

The pitfall of Visitor Pattern:

It is intrusive, and anti-pattern

The visitor pattern use double-dispatch, that usually goes like this:
public class ParentDataModel
{
    public void accept(Visitor visitor)
    {
        visitor.visit(this);
    }
}

public class ChildDataModel extends ParentDataModel 
{
    // no need to implement accept() by the child itself
}

public class Visitor 
{
    public void visit(ParentDataModel model)
    {
        // do something with it
    }

    public void visit(ChildDataModel model)
    {
        // do something with it
    }
}
Why on earth a data model need to be aware of visitor? A data model should only hold the data relevant to the model.

Does not go well with existing objects from external frameworks

What if you need to do something with, say Number, Double, that are from the JDK.
Even say you're willing to make a wrapper around each object you need in your project, it's hell tedious, and think about how many classes you will have to refactor to get this to work.
public class NumberWrapper 
{
    private Number value;

    public void accept(Visitor visitor)
    {
        visitor.visit(value);
    }
}

public class DoubleWrapper 
{
    private Double value;

    public void accept(Visitor visitor)
    {
        visitor.visit(value);
    }
}

public class Visitor 
{
    public void visit(Number value)
    {
        // do something with it
    }

    public void visit(Double value)
    {
        // do something with it
    }
}

Solution: One class rules them all

public static class SuperConsumer implements Consumer
{
    private Map<Class, Consumer> consumers = new HashMap<>();
    private Consumer unknown = o -> System.err.println("Unknown object type");

    public SuperConsumer()
    {
        consumers.put(Number.class, o -> consumeNumber(o));
        consumers.put(Double.class, o -> consumeDouble(o));
    }

    private void consumeNumber(Number value)
    {
         System.out.printf("Consuming: %s\n", value.getClass().getName());
    }

    private void consumeDouble(Double value)
    {
         System.out.printf("Consuming: %s\n", value.getClass().getName());
    }

    private Consumer findConsumer(Object object)
    {
        Consumer consumer = consumers.get(object.getClass());

        Class superClazz = object.getClass().getSuperclass();
        while (consumer == null && superClazz != Object.class)
        {
            consumer = consumers.get(superClazz);
            superClazz = superClazz.getSuperclass();
        }

        Class[] interfaces = object.getClass().getInterfaces();
        for (int i = 0; consumer == null && i < interfaces.length; i++)
        {
            consumer = consumers.get(interfaces[i]);
        }

        return consumer;
    }

    @Override
    public void accept(Object object)
    {
        Consumer consumer = findConsumer(object);
        if (consumer == null)
        {
            consumer = unknown;
        }
        consumer.accept(object);
    }

    public static void main(String[] args)
    {
        Consumer consumer = new SuperConsumer();
        Arrays.asList(new Double(1.0), new Integer(1), new Float(1.0f)).forEach(o -> consumer.accept(o));
    }
}

Copied from my answer on StackOverflow to question: Generified implementation of Visitor pattern in Java

No comments:

Post a Comment