Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive Generic and Fluent Interface

tl;dr

Trying to implement a hierarchal fluent interface such that I can combine nodes child classes while also the class standalone, but getting type parameter is not within its bound errors.

Details

I'm trying to achieve a solution so that I can create something such that I can do something like:

farm
    .animal()
        .cat()
            .meow()
            .findsHuman()
                .saysHello()
                .done()
            .done()
        .dog()
            .bark()
            .chacesCar()
            .findsHuman()
                .saysHello()
                .done()
            .done()
        .done()
    .human()
        .saysHello()
        .done();

while also being able to do:

Human human = new Human()
    .saysHello()

I've gotten close using various strategies but haven't been able to gain the flexibility described.

My current attempt uses the following classes:

abstract class Base<T extends Base<T>>{

    private T parent;

    Base(){

    }

    Base( T parent ){
        this.parent = parent;
    }

    public T done() throws NullPointerException{
        if ( parent != null ){
            return (T) parent;
        }

        throw new NullPointerException();
    }   
}

class Farm<T extends Base<T>> extends Base{

    private Animal<Farm<T>> animal;
    private Human<Farm<T>> human;

    public Farm(){
        super();
        this.animal = new Animal( this );
        this.human = new Human( this );
    }

    public Animal<Farm> animal(){
        return this.animal;
    }

    public Human<Farm<T>> human(){
        return this.human;
    }
}

class Animal <T extends Base<T>> extends Base{

    private Cat<Animal<T>> cat;
    private Dog<Animal<T>> dog;

    public Animal(){
        super();
        init();
    }

    public Animal( T parent ){
        super( parent );
        init();
    }

    private void init(){
        this.cat = new Cat(this);
        this.dog = new Dog(this);
    }

    public Cat<Animal<T>> cat(){
        return cat;
    }

    public Dog<Animal<T>> dog(){
        return dog;
    }
}

class Human<T extends Base<T>> extends Base{

    public Human<T> saysHello(){
        System.out.println("human says hi");
        return this;
    }
}

class Cat <T extends Base<T>> extends Base{

    private Human<Cat> human;

    public Cat(){
        super();
        init();
    }

    public Cat( T parent ){
        super( parent );
        init();
    }

    private void init(){
        this.human = new Human();
    }

    public Cat<T> meow(){
        System.out.println("cat says meow");
        return this;
    }

    public Human<Cat<T>> findsHuman(){
        return this.human;
    }
}


class Dog <T extends Base<T>> extends Base{

    private Human<Dog> human;

    public Dog(){
        super();
        init();
    }

    public Dog( T parent ){
        super( parent );
        init();
    }

    private void init(){
        this.human = new Human();
    }


    public Dog<T> bark(){
        System.out.println("dog says woof");
        return this;
    }

    public Dog<T> chacesCar(){
        System.out.println("cat drinks milk");
        return this;
    }

    public Human<Dog<T>> findsHuman(){
        return this.human;
    }

}

The errors I'm seeing are commonly:

Animal.java:4: type parameter Animal is not within its bound private Cat cat; Animal.java:5: type parameter Animal is not within its bound private Dog dog;

Applied to all similar references and also pertaining to my example desired case:

cannot find symbol symbol : method dog() location: class Base.dog()

I've tried using the following solutions which seemed to tackle similar problems, but to no avail, so any and all support is welcome.

References

  • Is there a way to refer to the current type with a type variable?

  • http://vyazelenko.com/2012/03/02/recursive-generics-to-the-rescue/

like image 534
SS44 Avatar asked Oct 10 '14 17:10

SS44


3 Answers

The code below seems to work fine and doesn't need any @SuppressWarnings. The key concept to grasp is that your T parameter is effectively the class of your object's parent, but T's parent could be anything. So instead of T extends Base<T> you want T extends Base<?>.

The output is:

cat says meow
human says hi
dog says woof
cat drinks milk
human says hi
human says hi

...which I believe is correct, although you might want to change your Dog.chacesCar() method so it doesn't output cat drinks milk! Also it should be chases not chaces.

Hope this helps!

abstract class Base<T extends Base<?>> {

    private final T parent;

    Base() {
        this.parent = null;
    }

    Base(T parent) {
        this.parent = parent;
    }

    public T done() throws NullPointerException {
        if (parent != null) {
            return parent;
        }

        throw new NullPointerException();
    }
}

class Farm<T extends Base<?>> extends Base<T> {

    private final Animal<Farm<T>> animal;
    private final Human<Farm<T>> human;

    public Farm() {
        super();
        this.animal = new Animal<>(this);
        this.human = new Human<>(this);
    }

    public Animal<Farm<T>> animal() {
        return this.animal;
    }

    public Human<Farm<T>> human() {
        return this.human;
    }
}

class Animal<T extends Base<?>> extends Base<T> {

    private Cat<Animal<T>> cat;
    private Dog<Animal<T>> dog;

    public Animal() {
        super();
        init();
    }

    public Animal(T parent) {
        super(parent);
        init();
    }

    private void init() {
        this.cat = new Cat<>(this);
        this.dog = new Dog<>(this);
    }

    public Cat<Animal<T>> cat() {
        return cat;
    }

    public Dog<Animal<T>> dog() {
        return dog;
    }
}

class Human<T extends Base<?>> extends Base<T> {
    public Human() {
        super();
    }

    public Human(T parent) {
        super(parent);
    }

    public Human<T> saysHello() {
        System.out.println("human says hi");
        return this;
    }
}

class Cat<T extends Base<?>> extends Base<T> {

    private Human<Cat<T>> human;

    public Cat() {
        super();
        init();
    }

    public Cat(T parent) {
        super(parent);
        init();
    }

    private void init() {
        this.human = new Human<>(this);
    }

    public Cat<T> meow() {
        System.out.println("cat says meow");
        return this;
    }

    public Human<Cat<T>> findsHuman() {
        return this.human;
    }
}

class Dog<T extends Base<?>> extends Base<T> {

    private Human<Dog<T>> human;

    public Dog() {
        super();
        init();
    }

    public Dog(T parent) {
        super(parent);
        init();
    }

    private void init() {
        this.human = new Human<>(this);
    }

    public Dog<T> bark() {
        System.out.println("dog says woof");
        return this;
    }

    public Dog<T> chacesCar() {
        System.out.println("cat drinks milk");
        return this;
    }

    public Human<Dog<T>> findsHuman() {
        return this.human;
    }

}

Test code:

public static void main(String[] args) {
    Farm<?> farm = new Farm<>();
    farm
        .animal()
            .cat()
                .meow()
                .findsHuman()
                    .saysHello()
                    .done()
                .done()
            .dog()
                .bark()
                .chacesCar()
                .findsHuman()
                    .saysHello()
                    .done()
                .done()
            .done()
        .human()
            .saysHello()
            .done();

    Human human = new Human()
            .saysHello();
}
like image 74
Constantinos Avatar answered Oct 07 '22 01:10

Constantinos


The best thing I came up is the following:

new Animal()
    .cat()
      .meow()
      .findsHuman()
        .<Cat>done()
      .<Animal>done()
    .dog()
      .bark()
        .findHuman()
          .<Dog>done()
      .done();

With the following base class:

public abstract class Base<T extends Base<T>>{

  private Base<?> backRef;

  public Base() {}

  public Base(Base<?> backRef) {
    this.backRef = backRef;
 }

  @SuppressWarnings("unchecked")
    protected T self() {
    return (T)this;
  }

  @SuppressWarnings("unchecked")
  public <U extends Base<U>> U done() {
    return (U)backRef;
  }
}

If you declare backRef as of Type T then the other classes are not allowed because they are not a subclasses of each other, so you have to specify a different type, but since this type is context dependent (one time its Cat, one time its Dog) I don't see an alternative as to pass a hint.

I found a solution:

new Animal()
    .cat()
      .meow()
      .findsHuman()
        .done()
      .done()
    .dog()
      .bark()
        .findHuman()
          .done()
    .done();



public abstract class Base<T extends Base<T,P>, P>{

  private P backRef;
  public Base() {}

  public Base(P backRef) {
    this.backRef = backRef;
  }

  @SuppressWarnings("unchecked")
  protected T self() {
    return (T)this;
  }

  public P done() {
    return backRef;
 }
}

Like someone suggested, we add an additional Type for the parent.

Now the base classes:

public final class Cat extends Base<Cat, Animal>{

  public Cat() {}

  public Cat(Animal backRef) {
    super(backRef);
  }

  public Cat meow() {
    System.out.println("Meeeoooww");
    return self();
  }

  public Human<Cat> findsHuman() {
    return new Human<Cat>(this);
  }
}

As you can see, Cat clearly specifies which base type it should use. Now for human, which can change the type depending on the context:

public final class Human<P> extends Base<Human<P>, P> {

  public Human() {}

  public Human(P backRef) {
    super(backRef);
  }

}

Human specifies an additional generic which the caller (Cat, Dog) specifies in their findHuman() Method.

like image 29
morpheus05 Avatar answered Oct 06 '22 23:10

morpheus05


This is what we did on one our project:

public abstract class Parent<T extends Parent<T>> {

    /**
     * Get {@code this} casted to its subclass.
     */
    @SuppressWarnings("unchecked")
    protected final T self() {
        return (T) this;
    }

    public T foo() {
        // ... some logic
        return self();
    }

    // ... other parent methods

}

public class Child extends Parent<Child> {

    public Child bar() {
        // ... some logic
        return self();
    }

    // ... other child methods

}

Allowing child to have its own subclass would be:

public class Child<T extends Child<T>> extends Parent<T> {

    public T bar() {
        // ... some logic
        return self();
    }

}
like image 37
Pavel Horal Avatar answered Oct 07 '22 00:10

Pavel Horal