Recursive Generic and Fluent Interface
Asked Answered
P

7

9

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

Peal answered 10/10, 2014 at 17:4 Comment(4)
IMHO you are using java generics for what they are not designed. This would work fine in C++, but java generics are not the same. Generics are Harmful, and this is one example. Surely there are other software engineering solutions for your needs, instead of the programming art you are pasting. When things are so much complex as the answers shown here, probably something is wrong.Demurrer
I think I found a solution. I updated my answer accordingly.Leman
@AlfonsoNishikawa I concur that it may be pushing the limits of this pattern, and was beyond some of my initial understanding's so thought I'd ask for the communities thoughts.Peal
@Leman Thank you for the update, I will go through all answers and see if I can find one that best solves me issue.Peal
G
4

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();
}
Giff answered 15/10, 2014 at 10:0 Comment(6)
Could you please share your main method, when trying I'm running into cannot find symbol errorsPeal
I've added it to the answer (it was pretty much the exact test code you had posted).Giff
What symbol is it not finding?Giff
Thank you, seems like my problem was I was running Farm without <?> which was causing done to return Base. This seems to resolve my problem and give me the direction I needed.Peal
The thing with SuppressWarnings in other answers is there to solve class hierarchy and template methods. For example you have to reimplement findsHuman() in both Cat and Dog instead of implementing it in the Animal class. Nevertheless +1 for approaching the question from the other way around.Lutero
To be more clear - you have changed the semantics of the generic parameter from "being the leaf child type" to "being the owning builder type" (that is what I meant by approach from the other way around).Lutero
L
3

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.

Leman answered 14/10, 2014 at 13:59 Comment(2)
That is quite interesting use of type hints (although the type safety of this is not that good). +1Lutero
Congratulations. I hope I will never need something like this ;) But good job. Personally, I could never get into the solution.Demurrer
L
2

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

}
Lutero answered 14/10, 2014 at 13:41 Comment(2)
the implementation of self should probably be final so no one else messes around with that.Winni
This would not work for OtherChild extends Parent<Child> - the (T) this in self() would fail.Kennie
S
1

In this line:

class Farm<T extends Base<T>>

The compiler treats the second type parameter as a concrete class. For example, say if you replaced that line with this:

class Farm<T extends Base<Double>>

'Double' is a concrete class. When the compiler scans this, it cannot tell the difference between your T and Double, and such treats them both as concrete class, and not type parameters. The only way to let the compiler know T is a type parameter is this way:

class Farm<T extends Base<T>, T>

I hope this answers (or is at least relevant) to your question.

edit Post was edited while I was typing, so I guess this answer isn't relevant anymore.

Squinty answered 14/10, 2014 at 13:43 Comment(0)
D
1

You could also play with interfaces, so that you can fake multiple inheritance. A bit verbose, but there is no hazardous casting, and I find it quite understandable.


Define the available methods:

public interface AnimalIn {
    AnimalOut animal();
}

public interface CatIn {
    CatOut cat();
}

public interface MeowIn {
    CatOut meow();
}

public interface DogIn {
    DogOut dog();
}

public interface BarkIn {
    DogOut bark();
}

public interface ChacesCarIn {
    DogOut chacesCar();
}

public interface FindsHumanIn<T> {
    HumanOut<T> findsHuman();
}

public interface HumanIn {
    HumanOut<FarmOut> human();
}

public interface SaysHelloIn<T> {
    HumanOut<T> saysHello();
}

public interface DoneIn<T> {
    T done();
}

You may need to have multiple methods in an interface, but I haven't met this need yet. For example, if you had had to kinds of meows:

public interface MeowIn {
    CatOut meowForFood();
    CatOut meowForMilk();
    CatOut meowForStrokes();
}

Define the output types:

Farm provides Animal or Human:

public interface FarmOut extends AnimalIn, HumanIn {
    // no specific methods
}

Animal provides Cat, Dog or Done:

public interface AnimalOut extends CatIn, DogIn, DoneIn<FarmOut> {
    // no specific methods
}

Cat provides Meow, FindsHuman or Done:

public interface CatOut extends MeowIn, FindsHumanIn<CatOut>, DoneIn<AnimalOut> {
    // no specific methods
}

Dog provides Bark, ChacesCar, FindsHuman or Done:

public interface DogOut extends BarkIn, ChacesCarIn, FindsHumanIn<DogOut>, DoneIn<AnimalOut> {
    // no specific methods
}

Human provides SayHello or Done:

public interface HumanOut<T> extends SaysHelloIn<T>, DoneIn<T> {
    // no specific methods
}

Simply implement the *Out interfaces:

public class Farm implements FarmOut {

    @Override
    public AnimalOut animal() {
        return new Animal(this);
    }

    @Override
    public HumanOut<FarmOut> human() {
        return new Human<FarmOut>(this);
    }

}

public class Animal implements AnimalOut {

    private FarmOut chain;

    public Animal(FarmOut chain) {
        this.chain = chain;
    }

    @Override
    public CatOut cat() {
        return new Cat(this);
    }

    @Override
    public DogOut dog() {
        return new Dog(this);
    }

    @Override
    public FarmOut done() {
        return chain;
    }

}

public class Dog implements DogOut {

    private AnimalOut chain;

    public Dog(AnimalOut chain) {
        this.chain = chain;
    }

    @Override
    public DogOut bark() {
        System.out.println("bark");
        return this;
    }

    @Override
    public DogOut chacesCar() {
        System.out.println("chaces car");
        return this;
    }

    @Override
    public HumanOut<DogOut> findsHuman() {
        return new Human<DogOut>(this);
    }

    @Override
    public AnimalOut done() {
        return chain;
    }

}

public class Cat implements CatOut {

    private AnimalOut chain;

    public Cat(AnimalOut chain) {
        this.chain = chain;
    }

    @Override
    public CatOut meow() {
        System.out.println("meow");
        return this;
    }

    @Override
    public HumanOut<CatOut> findsHuman() {
        return new Human<CatOut>(this);
    }

    @Override
    public AnimalOut done() {
        return chain;
    }

}

public class Human<T> implements HumanOut<T> {

    private T chain;

    public Human(T chain) {
        this.chain = chain;
    }

    @Override
    public HumanOut<T> saysHello() {
        System.out.println("hello");
        return this;
    }

    @Override
    public T done() {
        return chain;
    }

}

Those implementations would work also without the interfaces: remove the implements *Out, the @Overrides, and replace any *Out by * (e.g. AnimalOut by Animal). That said, it's easier to maintain with the interfaces: simply update them and fix your compilation errors. It's also easier to find DSL solutions with interfaces (as you can see), and they are sometimes simply necessary.


Demo:

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

Prints:

meow
hello
bark
chaces car
hello
hello
Dorfman answered 14/10, 2014 at 14:34 Comment(0)
W
1

Your problem is that the method done should return the parent, but the parent is not necessarily a T but is just a Base. And the other problem is that whatever the class is, the done method should always return the same class.

But here is a slight variation of your proposed classes. First for Base declaring its concrete class and its concrete parent :

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

    private P parent;

    Base(){

    }

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

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

        throw new NullPointerException();
    }   
}

That being done, the derived concrete classes become :

class Farm extends Base<Farm, Object>{

    private Animal animal;
    private Human human;

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

    public Animal animal(){
        return this.animal;
    }

    public Human human(){
        return this.human;
    }
}

class Animal extends Base<Animal, Farm>{

    private Cat cat;
    private Dog dog;

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

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

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

    public Cat cat(){
        return cat;
    }

    public Dog dog(){
        return dog;
    }
}

class Human extends Base<Human, Farm>{

    public Human() {

    }

    public Human(Farm farm) {
        super(farm);
    }

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

}

class CatOrDog extends Base<Cat, Animal>{

    protected Human human;

    public CatOrDog(){
        super();
        init(null);
    }

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

    private void init(Animal parent){
        Animal parent = done();
        Farm farm = (parent == null) ? null : parent.done();
        this.human = new Human(farm);
    }

    public Human findsHuman(){
        return this.human;
    }
}


class Cat extends CatOrDog{

    public Cat(){
        super();
    }

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

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


class Dog extends CatOrDog {

    public Dog(){
        super();
    }

    public Dog( Animal parent ){
        super( parent );
    }

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

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

With that, I could write without any error or warning :

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

But note that I had to replace to done calls with animals calls.

Edit :

I added a new class CatOrDog to factorize the Human processing. As the parent of a Human is a Farm, I initialize the new human with a correct parent if it exists. That way, not only the above sources compiles without error or warning, but it also runs without any problem and it prints :

cat says meow
human says hi
dog says woof
cat drinks milk
human says hi
human says hi
Weigh answered 14/10, 2014 at 14:40 Comment(0)
M
0

There is no "safe" way to do this, but this should compile:

class Dog extends Base{

 <T extends Dog> T bark(){
    return (T) this;
 } 

}
Marje answered 14/10, 2014 at 13:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.