【问题标题】:Generified implementation of Visitor pattern in JavaJava中访问者模式的通用实现
【发布时间】:2016-04-01 18:14:21
【问题描述】:

我进行了一些研究,试图开发一个类型转换框架,该框架提供将源类(例如,Foo)的实例转换为结果类(例如,Bar 或 Baz)的实例的能力。该框架应该能够为同一对源和结果使用不同的转换逻辑(即不同的转换器)。它还应该是可扩展的,即允许为新的和现有的源和结果对添加新的转换器。另一个要求是类型安全,即任何尝试将某个源类的实例转换为结果类的实例而没有转换器实现适当的转换逻辑的任何尝试都应该导致编译时错误。

我决定使用Visitor pattern,将转换器作为访客,将可转换类作为元素。为了提供可扩展性和类型安全性,我决定使用泛型。所以我做的转换框架的第一个实现是受到互联网上一些文章的影响(不幸的是我失去了链接)是......

带有全状态转换器的转换框架

以下是框架的核心接口,Converter和Convertable:

    public interface Converter<V extends Converter<V,A>, A extends Convertable<V,A>> {

        void convert(A convertable);
    }


    public interface Convertable<V extends Converter<V,A>, A extends Convertable<V,A>> {

        void convertWith(V converter);
    }

泛型使Convertable 的实现只接受可以转换它们的Converter 的实现,并使Converter 的实现只访问它被转换的Convertable 的实现。以下是此类转换器的示例:

interface FooConverter extends Converter<FooConverter,Foo> {

    void convert(Foo convertable);

    void convert(FooChild1 convertable);

    void convert(FooChild2 convertable);
}


public class Foo2BarConverter implements FooConverter {

    private Bar result;

    public Bar getResult() {
        return result;
    }

    @Override
    public void convert(Foo convertable) {
        this.result = new Bar("This bar's converted from an instance of Foo");
    }

    @Override
    public void convert(FooChild1 convertable) {
        this.result = new Bar("This bar's converted from an instance of FooChild1");
    }

    @Override
    public void convert(FooChild2 convertable) {
        this.result = new Bar("This bar's converted from an instance of FooChild2");
    }
}


public class Foo2BazConverter implements FooConverter {

    private Baz result;

    public Baz getResult() {
        return result;
    }

    @Override
    public void convert(Foo convertable) {
        this.result = new Baz("This baz's converted from an instance of Foo");
    }

    @Override
    public void convert(FooChild1 convertable) {
        this.result = new Baz("This baz's converted from an instance of FooChild1");
    }

    @Override
    public void convert(FooChild2 convertable) {
        this.result = new Baz("This baz's converted from an instance of FooChild2");
    }
}

以下是一些可以使用该转换器进行转换的类:

public class Foo implements Convertable<FooConverter, Foo> {

    @Override
    public void convertWith(FooConverter converter) {
        converter.convert(this);
    }
}


public class FooChild1 extends Foo {

    @Override
    public void convertWith(FooConverter converter) {
        converter.convert(this);
    }
}


public class FooChild2 extends Foo {

    @Override
    public void convertWith(FooConverter converter) {
        converter.convert(this);
    }
}

这里是结果类,即BarBaz

public class Bar {

    private String message;

    public Bar(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}


public class Baz {

    private String message;

    public Baz(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

这是一个测试转换器的代码:

Foo fooObj = new Foo();
Foo fooChild1Obj = new FooChild1();
Foo fooChild2Obj = new FooChild2();

// converting to bar
Foo2BarConverter foo2BarConverter = new Foo2BarConverter();

fooObj.convertWith(foo2BarConverter);
System.out.println(foo2BarConverter.getResult().getMessage());

fooChild1Obj.convertWith(foo2BarConverter);
System.out.println(foo2BarConverter.getResult().getMessage());

fooChild2Obj.convertWith(foo2BarConverter);
System.out.println(foo2BarConverter.getResult().getMessage());

// converting to baz
System.out.println();
Foo2BazConverter foo2BazConverter = new Foo2BazConverter();

fooObj.convertWith(foo2BazConverter);
System.out.println(foo2BazConverter.getResult().getMessage());

fooChild1Obj.convertWith(foo2BazConverter);
System.out.println(foo2BazConverter.getResult().getMessage());

fooChild2Obj.convertWith(foo2BazConverter);
System.out.println(foo2BazConverter.getResult().getMessage());

以及由此代码构建的输出

This bar's converted from an instance of Foo
This bar's converted from an instance of FooChild1
This bar's converted from an instance of FooChild2

This baz's converted from an instance of Foo
This baz's converted from an instance of FooChild1
This baz's converted from an instance of FooChild2

查看Foo2BarConverterFoo2BazConverter 中的result 字段。这是实施的主要缺点。它使转换器有状态,这并不总是很方便。试图避免我开发的这个缺点......

没有双重调度的转换框架

此实现的要点是使用结果类对转换器进行参数化,并从Converterconvert 方法和ConvertableconvertWith 方法返回结果。这是它在代码中的样子:

public interface Converter<A extends Convertable<A>,R> {

    R convert(A convertable);
}

public interface Convertable<A extends Convertable<A>> {

    <R> R convertWith(Converter<A,R> converter);
}

public interface FooConverter<R> extends Converter<Foo,R> {

    @Override
    R convert(Foo convertable);

    R convert(FooChild1 convertable);

    R convert(FooChild2 convertable);
}

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 implements Convertable<Foo> {

    @Override
    public <R> R convertWith(Converter<Foo,R> converter) {
        return converter.convert(this);
    }
}

public class FooChild1 extends Foo {

    @Override
    public <R> R convertWith(Converter<Foo,R> converter) {
        return converter.convert(this);
    }
}

public class FooChild2 extends Foo {

    @Override
    public <R> R convertWith(Converter<Foo,R> converter) {
        return converter.convert(this);
    }
}

V 已从 Convertable 声明中删除,因为在 Converter 声明中包含结果类实际上会让我们使用结果类参数化 Convertable 的实现。它会将可转换的每个实现绑定到它可以转换为的唯一结果类。所以convertWith 中的Convertable 指的是带有Converter&lt;A,R&gt; 接口的接收转换器。这就是问题所在。现在调用接收转换器的Convertable 的实现将始终调用convert,它在Converter 接口中定义,而不是在convert 实现中覆盖它的convert 方法。换句话说,Foo2BarConverterFoo2BazConverter 中的 convert(FooChild1 convertable)convert(FooChild2 convertable) 永远不会被调用。基本上,它扼杀了访问者模式的主要概念,即双重调度。这是一个测试代码...

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());

及其输出表明在此实现中未调用覆盖方法。

This bar's converted from an instance of Foo
This bar's converted from an instance of Foo
This bar's converted from an instance of Foo

This baz's converted from an instance of Foo
This baz's converted from an instance of Foo
This baz's converted from an instance of Foo

下一个实现我尝试用 was... 制作无状态转换器。

带有参数化方法的转换器

这里的主要概念是仅对我想要返回转换结果的方法进行参数化,而不对接口声明进行参数化。

public interface Converter<V extends Converter<V,A>, A extends Convertable<V,A>> {

    <R> R convert(A convertable);
}

public interface Convertable<V extends Converter<V,A>, A extends Convertable<V,A>> {

    <R> R convertWith(V converter);
}

interface FooConverter extends Converter<FooConverter,Foo> {

    <R> R convert(Foo convertable);

    <R> R convert(FooChild1 convertable);

    <R> R convert(FooChild2 convertable);
}

public class Foo2BarConverter implements FooConverter {

    @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 {

    @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 implements Convertable<FooConverter, Foo> {

    @Override
    public <R> R convertWith(FooConverter converter) {
        return converter.convert(this);
    }
}

public class FooChild1 extends Foo {

    @Override
    public <R> R convertWith(FooConverter converter) {
        return converter.convert(this);
    }
}

public class FooChild2 extends Foo {

    @Override
    public <R> R convertWith(FooConverter converter) {
        return converter.convert(this);
    }
}

测试代码

Foo fooObj = new Foo();
Foo fooChild1Obj = new FooChild1();
Foo fooChild2Obj = new FooChild2();

// converting to bar
Foo2BarConverter foo2BarConverter = new Foo2BarConverter();
System.out.println(fooObj.<Bar>convertWith(foo2BarConverter).getMessage());
System.out.println(fooChild1Obj.<Bar>convertWith(foo2BarConverter).getMessage());
System.out.println(fooChild2Obj.<Bar>convertWith(foo2BarConverter).getMessage());

System.out.println();

// converting to baz
Foo2BazConverter foo2BazConverter = new Foo2BazConverter();
System.out.println(fooObj.<Baz>convertWith(foo2BazConverter).getMessage());
System.out.println(fooChild1Obj.<Baz>convertWith(foo2BazConverter).getMessage());
System.out.println(fooChild2Obj.<Baz>convertWith(foo2BazConverter).getMessage());

它的输出

This bar's converted from an instance of Foo
This bar's converted from an instance of FooChild1
This bar's converted from an instance of FooChild2

This baz's converted from an instance of Foo
This baz's converted from an instance of FooChild1
This baz's converted from an instance of FooChild2

乍一看看起来很棒。但实际上这个解决方案不是类型安全的。例如下面的调用

fooObj.<Baz>convertWith(foo2BarConverter).getMessage()

不会导致编译时错误。但这会导致运行时出现 ClassCastException。

所以一般问题如下。

有没有办法用 Java 制作无状态的泛型类型安全访问者?

UPD:我添加了所有三个实现的来源的链接:1st2nd3rd

【问题讨论】:

    标签: java generics design-patterns visitor-pattern


    【解决方案1】:

    您的转换器只是简单的函数,您可能不需要“框架”来组合它们。而你的第三次尝试没有多大意义:

    <R> R convertWith(V converter);
    

    意思是:“给定一些东西(对你想要的 R 一无所知的 V 转换器),给我任何东西(任意 R)”。正如您发现的那样,这不起作用。

    使用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();
    }
    

    如果你想要更多的框架,你可能想看看镜头:https://github.com/functionaljava/functionaljava/tree/master/core/src/main/java/fj/data/optic

    【讨论】:

    • 感谢您的回复。我想过这样的解决方案。除此之外,由于某些原因,我在帖子中故意省略了 Converter 和 Convertable (即使没有它也太大了),这些接口构成了框架的基础。没有它们,就会有单独的转换器相互连接。
    • Converter 基本上都是Function,你可以组合函数。将其添加到我的回复中。
    【解决方案2】:

    您可以放弃访问者模式,因为有更好的 Java 解决方案。

    访问者模式的陷阱:

    它具有侵入性和反模式

    访问者模式使用双重调度,通常是这样的:

    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
        }
    }
    

    为什么数据模型需要了解访问者?数据模型应该只保存与模型相关的数据。

    不适用于外部框架中的现有对象

    如果您需要对来自 JDK 的 Number、Double 等做一些事情。

    即使说您愿意为项目中需要的每个对象创建一个包装器,这也是非常乏味的,并考虑您必须重构多少类才能使其正常工作。

    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
        }
    }
    

    解决方案:一个班级统治所有班级

    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));
        }
    }
    

    【讨论】:

    • 如果使用消费者不支持的类型的对象调用消费者,是否会发生编译时错误?访问者模式的优势之一是类型安全。
    • 不,不会出现编译时错误,如果仔细观察,消费者会收到Object。对于不支持的类型,你只需提供一个后备消费者,甚至抛出异常,采用哪种策略取决于用例。
    猜你喜欢
    • 1970-01-01
    • 2017-09-17
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-07
    相关资源
    最近更新 更多