Generified implementation of Visitor pattern in Java

前端 未结 2 1036
情歌与酒
情歌与酒 2020-12-20 02:07

I\'ve made some research trying to develop a type conversion framework which provides an ability to convert instances of a source class (e.g., Foo) to instances of result cl

相关标签:
2条回答
  • 2020-12-20 02:40

    Your converters are simply functions, you probably do not need a "framework" to compose them. And your third try do not make much sense:

    <R> R convertWith(V converter);
    

    mean: "given something (the V converter that know nothing about the R you want), give me anything (arbitrary R)". As you found out this does not work.

    Simple Implementation using the corrected visitor pattern:

    interface FooConverter<R> extends Function<Foo, R> {
    
      R convert(Foo convertable);
    
      R convert(FooChild1 convertable);
    
      R convert(FooChild2 convertable);
    
      default R apply(Foo foo) { return foo.convertWith(this); }
    }
    
    public class Foo2BarConverter implements FooConverter<Bar> {
    
      @Override
      public Bar convert(Foo convertable) {
        return new Bar("This bar's converted from an instance of Foo");
      }
    
      @Override
      public Bar convert(FooChild1 convertable) {
        return new Bar("This bar's converted from an instance of FooChild1");
      }
    
      @Override
      public Bar convert(FooChild2 convertable) {
        return new Bar("This bar's converted from an instance of FooChild2");
      }
    }
    
    public class Foo2BazConverter implements FooConverter<Baz> {
    
      @Override
      public Baz convert(Foo convertable) {
        return new Baz("This baz's converted from an instance of Foo");
      }
    
      @Override
      public Baz convert(FooChild1 convertable) {
        return new Baz("This baz's converted from an instance of FooChild1");
      }
    
      @Override
      public Baz convert(FooChild2 convertable) {
        return new Baz("This baz's converted from an instance of FooChild2");
      }
    }
    
    public class Foo{
    
      public <R> R convertWith(FooConverter<R> converter) {
        return converter.convert(this);
      }
    }
    
    public class FooChild1 extends Foo {
    
      @Override
      public <R> R convertWith(FooConverter<R>  converter) {
        return converter.convert(this);
      }
    }
    
    public class FooChild2 extends Foo {
    
      @Override
      public <R> R convertWith(FooConverter<R> converter) {
        return converter.convert(this);
      }
    }
    
    public void test() {
      Foo fooObj = new Foo();
      Foo fooChild1Obj = new FooChild1();
      Foo fooChild2Obj = new FooChild2();
    
      // converting to bar
      Foo2BarConverter foo2BarConverter = new Foo2BarConverter();
      System.out.println(fooObj.convertWith(foo2BarConverter).getMessage());
      System.out.println(fooChild1Obj.convertWith(foo2BarConverter).getMessage());
      System.out.println(fooChild2Obj.convertWith(foo2BarConverter).getMessage());
    
      System.out.println();
    
      // converting to baz
      Foo2BazConverter foo2BazConverter = new Foo2BazConverter();
      System.out.println(fooObj.convertWith(foo2BazConverter).getMessage());
      System.out.println(fooChild1Obj.convertWith(foo2BazConverter).getMessage());
      System.out.println(fooChild2Obj.convertWith(foo2BazConverter).getMessage());
    
      // does not compile:
      fooObj.<Baz>convertWith(foo2BarConverter).getMessage();
    }
    

    Then if you want some more framework, you may want to look into lenses: https://github.com/functionaljava/functionaljava/tree/master/core/src/main/java/fj/data/optic

    0 讨论(0)
  • 2020-12-20 02:58

    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));
        }
    }
    
    0 讨论(0)
提交回复
热议问题