Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
585 views
in Technique[技术] by (71.8m points)

generics - Generified implementation of Visitor pattern in Java

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

    @Overrid

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...