Search code examples
javavisitor-pattern

How to correctly use the visitor pattern with different Shapes for an Editor


I am creating a editor for different Shape Objects. I stumbled upon the visitor pattern which actually fits my needs I think.
I have a Element class which holds a field named attrs

public class Element {
    ...   
    private Shape attrs;
    ...
}

My Shape class looks like this with the visitor design pattern.

public abstract class Shape {
    public abstract void accept(ShapeVisitor v);

    public interface ShapeVisitor{
        public void visit(CircleObject circle);
        public void visit(RectangleObject rectangle);
    }
}

And the actual instance of a Shape is defined in a RectangleObject class which extends Shape and holds the Rectangle field. The reasoning for this structure is that I am serializing and deserializing with Jackson for a specifig JSON layout.

public class RectangleObject extends Shape {

    private Rectangle rect;

    public class Rectangle {
        private String fill;

        public String getFill() {
            return fill;
        }
        public void setFill(String fill) {
            this.fill = fill;
        }

    @Override
    public void accept(JointShapeVisitor v) {
        v.visit(this);
      }
    }

And finally my Editor implements the vistor methods.

public class Editor implements ShapeVisitor{
@Override
    public void visit(CircleObject circle) {

    }

    @Override
    public void visit(RectangleObject rectangle) {

    }


    public void setComponent(JsonArray arguments){
      Element element = getFromJson(arguments);
      visit(element.getAttrs()); // *** this does obv. not work ***
    }
}

The element.getAttrs() returns JointShape, but I need here a CircleObject or a RectangleObject.


How can I pass the correct instance of the ShapeObject to the visit method? Am I doing something completely wrong?

Best regards.


Solution

  • What you would do is

    public void setComponent(JsonArray arguments){
      Element element = getFromJson(arguments);
      element.getAttrs().accept(this);
    }
    

    And you'll get a callback into one of the visit methods. What you don't get is a return value.

    This can be a bit tricky to handle because the callback code is suddenly in no relation to the method that called the accept method. But you often want to pass arguments back and forth between the visiting method and the method that that called accept. To do that, you can add a little to the pattern:

    @Override
    public Object accept(JointShapeVisitor v, Object context) {
        return v.visit(this, context);
      }
    }
    
    public interface ShapeVisitor{
        public Object visit(CircleObject circle, Object context);
        ..
    }
    

    Maybe with a bit of generics to make it typesafe. But even without you can suddenly do

    public class Editor implements ShapeVisitor{
        @Override
        public Foo visit(CircleObject circle, Object context) {
              return new Foo(circle, (String) context));
        }
    
        @Override
        public void visit(RectangleObject rectangle) {
    
        }
    
    
        public void setComponent(JsonArray arguments){
          Element element = getFromJson(arguments);
          Foo foo = (Foo)element.getAttrs().visit(this, "Hello");
        }
    }