Implementación genérica del patrón Visitor en Java

He realizado algunas investigaciones tratando de desarrollar un marco de conversión de tipos que brinde la capacidad de convertir instancias de una clase fuente (por ejemplo, Foo) en instancias de clases de resultados (por ejemplo, Bar o Baz). El marco debe proporcionar la capacidad de utilizar diferentes lógicas de conversión (es decir, diferentes convertidores) para el mismo par de fuente y resultado. También debe ser extensible, es decir, permitir agregar nuevos convertidores para pares nuevos y existentes de una fuente y resultado. Un requisito más es la seguridad de tipo, es decir, cualquier intento de convertir una instancia de alguna clase de origen en una instancia de una clase de resultado sin que el convertidor implemente la lógica de conversión adecuada debería conducir a un error de tiempo de compilación.

Decidí usar unPatrón de visitante con convertidores como visitantes y clases convertibles como elementos. Para proporcionar extensibilidad y seguridad de tipos, decidí usar genéricos. Entonces, la primera implementación del marco de conversión que hice influenciado por algún artículo en Internet (desafortunadamente perdí el enlace) fue ...

Marco de conversión con convertidores con estado

Estas son las interfaces principales del marco, convertidor y convertible:

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

Los genéricos hacen una implementación deConvertable aceptar solo implementaciones deConverter que puede convertirlos y hacer una implementación deConverter visite solo implementaciones deConvertable que está hecho para convertir. Aquí hay un ejemplo de tales convertidores:

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

Y aquí hay algunas clases que podrían convertirse con esos convertidores:

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

Aquí están las clases de resultados, es decir, <, código> Bar yBaz:

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

Y aquí hay un código que prueba que los convertidores:

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

y salida construida por este código

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

Echa un vistazo aresult campo enFoo2BarConverter yFoo2BazConverter. Ese es el principal inconveniente de la implementación. Hace convertidores con estado que no siempre es útil. Tratando de evitar este inconveniente que desarrollé ...

Marco de conversión sin doble despacho

El punto principal de esta implementación es parametrizar convertidores con clases de resultados y devolver resultados deconvert método deConverter yconvertWith método deConvertable. Así es como se ve en el código:

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 se elimina deConvertable declaración porque tener una clase de resultado enConverter declaración nos tendría de hecho para parametrizar implementaciones deConvertable con clases de resultados. Limitaría cada implementación de convertible a la única clase de resultado a la que podría convertirse. EntoncesconvertWith enConvertable refiere convertidores recibidos conConverter<A,R> interfaz. Y ese es el problema. Ahora implementaciones deConvertable invocar el convertidor recibido siempre invocaráconvert que se define enConverter interfaz y noconvert métodos que lo anulan enConverter implementaciones En otras palabrasconvert(FooChild1 convertable) yconvert(FooChild2 convertable) enFoo2BarConverter yFoo2BazConverter nunca será llamado Básicamente, mata la noción principal del patrón Visitante, el despacho doble. Aquí hay un código de prueba ...

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

y su salida que demuestra que los métodos de anulación no se invocan en esta implementación.

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

La siguiente implementación con la que intenté hacer convertidores sin estado fue ...

Convertidores con métodos parametrizados.

La noción principal aquí es parametrizar solo los métodos que quiero devolver un resultado de conversión sin parametrizar las declaraciones de las interfaces.

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

Código de prueba

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

y es salida

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

A primera vista se ve genial. Pero, de hecho, esta solución no es segura. Por ejemplo la siguiente llamada

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

no causaría un error de tiempo de compilación. Pero conduciría a ClassCastException en tiempo de ejecución.

Entonces la pregunta general es la siguiente.

¿Hay alguna manera de hacer un visitante sin tipo generado sin texto con Java?

UPD: He agregado enlaces a las fuentes de las tres implementaciones:Primero, 2do y3ro

Respuestas a la pregunta(2)

Su respuesta a la pregunta