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 classes (e.g., Bar or Baz). The framework should provide an ability
to use different conversion logic (i.e., different converters) for the same pair of a source and result. It also should be extendable, i.e. allow of adding new converters for new and existing pairs of a source and result. One more
requirement is typesafety, i.e. any attempt to convert an instance of some source class to an instance of a result class without converter implementing appropriate conversion logic should lead to compile time error.
I decided to use a Visitor pattern with converters as Visitors and convertable classes as Elements. To provide extendability and typesafety I decided to use generics. So the first
implementation of the conversion framework I made being influenced by some article in the Internet (unfortunately I've lost the link) was...
Conversion framework with statefull converters
Here are the core interfaces of the framework, Converter and 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);
}
Generics make an implementation of Convertable
accept only implementations of Converter
which can convert them and make an implementation of Converter
visit only implementations of Convertable
which it's made to convert. Here is an example of such converters:
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");
}
}
And here are some classes which could be converted with that converters:
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);
}
}
Here are result classes, i.e. Bar
and Baz
:
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;
}
}
And here is a code which tests that converters:
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());
and output built by this code
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
Take a look at result
field in Foo2BarConverter
and Foo2BazConverter
. That's the main drawback of the implementation. It makes converters statefull which is not always handy. Trying to avoid this drawback I developed...
Conversion framework without double dispatching
The main point of this implementation is to parametrize converters with result classes and return results from convert
method of Converter
and convertWith
method of Convertable
. Here is how it looks in code:
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
is removed from Convertable
declaration because having a result class in Converter
declaration it would have us in fact to parametrize implementations of Convertable
with result classes. It would bound each implementation of convertable to the only result class it could be converted to. So convertWith
in Convertable
refers received converters with Converter<A,R>
interface. And that's the problem. Now implementations of Convertable
invoking received converter will allways invoke convert
which is defined in 'Converter' interface and not convert
methods which override it in Converter
implementations. In other words convert(FooChild1 convertable)
and convert(FooChild2 convertable)
in Foo2BarConverter
and Foo2BazConverter
will never be called. Basically, it kills the main notion of Visitor pattern, double dispatching. Here is a test code...
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());
and its output which demonstrates that overriding methods aren't called in this implementation.
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
Next implementation I tried to make stateless converters with was...
Converters with parametrized methods
The main notion here is to parametrize only methods which I want to return a conversion result without parametrizing declarations of 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);
}
}
Testing code
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());
and it's output
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
At first glance looks great. But in fact this solution isn't typesafe. For example the following call
fooObj.<Baz>convertWith(foo2BarConverter).getMessage()
wouldn't cause a compile time error. But it would lead to ClassCastException in runtime.
So the general question is the following.
Is there a way to make a stateless generifyed typesafe Visitor with Java?
Aucun commentaire:
Enregistrer un commentaire