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
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
You could just ditch Visitor Pattern as there is a better solution for Java.
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.
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
}
}
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));
}
}