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