7

My question is in context of Method chaining + inheritance don’t play well together?. But unfortunately all examples/answers of method chaining uses single level of inheritance. My usecase involves multi level of inheritance for e.g

abstract class PetBuilder{...}
class  DogBuilder extends PetBuilder{..}
class DogType1Builder extends DogBuilder {...}

To construct a Dog Object,i will be using either DogBuilder or DogType1Builder

how to use getThis trick for the above use case?

I want to use builder pattern for constructing a complicated Dog object(Dog Object Model)". DogType1 will have some added properties.

so to use getThis Trick declaration of above classes will become like

abstract class PetBuilder<T extends PetBuilder<T>>
class DogBuilder<T extends DogBuilder<T>> extends PetBuilder<DogBuilder<T>>
class DogType1Builder extends DogBuilder<DogType1Builder>

Now this creates two problems

1.builder method in 'DogBuilder' will look like

public T someMethodInDog(String dogName) {
..
return (T)this; ///i dont want type casting and i cant use getThis Trick Here (compiler reports error for conversion from DogBuilder to T)
}

2.As DogBuilder has become parameterised,so to create instance of "DogBuilder" i will have to use

DogBuilder<DogBuilder> builder=new DogBuilder(); //passing <DogBuilder> type ...real pain

Is there a better way?

Community
  • 1
  • 1
harrybvp
  • 2,445
  • 2
  • 22
  • 30
  • 2
    Have you actually tried it? I don't see any reason why the same trick shouldn't work with multiple levels of inheritance. If you have a specific problem, please clarify and - preferably - give a concrete code example. – Péter Török Mar 11 '12 at 13:34
  • i have edited my question to clarify the problem – harrybvp Mar 11 '12 at 14:52

2 Answers2

8

The root of your problem is a class design issue: you are trying to inherit from a concrete class, which is almost always a mistake, and (your case in point) is bound to cause numerous problems. To stick with the example given in the referred thread, you should not be instantiating Dog, as in such a universe there can exist no Dogs in general, any more than Pets - only Poodles, NewFoundlands, Spaniels etc. Consequently, getThis should not be implemented in mid-level (abstract) classes, only in the (concrete) leaf classes. And in all mid-level abstract classes, you should only refer to the generic type parameter T, instead of the actual class name.

Here is the example in the answer to the referred thread rewritten according to the above rules:

public class TestClass {

  static abstract class Pet <T extends Pet<T>> {
    private String name;

    protected abstract T getThis();

    public T setName(String name) {
      this.name = name;
      return getThis(); }
  }

  static class Cat extends Pet<Cat> {
    @Override protected Cat getThis() { return this; }

    public Cat catchMice() {
      System.out.println("I caught a mouse!");
      return getThis();
    }
  }

  // Dog is abstract - only concrete dog breeds can be instantiated
  static abstract class Dog<T extends Dog<T>> extends Pet<T> {
    // getThis is not implemented here - only in concrete subclasses

    // Return the concrete dog breed, not Dog in general
    public T catchFrisbee() {
      System.out.println("I caught a frisbee!");
      return getThis();
    }
  }

  static class Poodle extends Dog<Poodle> {
    @Override protected Poodle getThis() { return this; }

    public Poodle sleep() {
      System.out.println("I am sleeping!");
      return getThis();
    }
  }

  static class NewFoundland extends Dog<NewFoundland> {
    @Override protected NewFoundland getThis() { return this; }

    public NewFoundland swim() {
      System.out.println("I am swimming!");
      return getThis();
    }
  }

  public static void main(String[] args) {
    Cat c = new Cat();
    c.setName("Morris").catchMice();
    Poodle d = new Poodle();
    d.setName("Snoopy").catchFrisbee().sleep();
    NewFoundland f = new NewFoundland();
    f.setName("Snoopy").swim().catchFrisbee();
  }
}
Community
  • 1
  • 1
Péter Török
  • 114,404
  • 31
  • 268
  • 329
  • Actually all my classes act as builder constructing a complicated object(Pet Object Model). so some builder properties of that object are exposed via DogBuilder and i want to reuse that in DogType1Builder (which itself has some more builder properties) which is a fair requirement. and since My Pet Object Model has a complicated object graph ,so i want to use a builder pattern for constructing it. – harrybvp Mar 11 '12 at 15:36
  • @harrybvp, I believe what I described above can be applied to a builder hierarchy too, as long as the corresponding class hierarchy obeys the same rules. I.e. if you do not want to construct generic `Dog` objects, only instances of concrete dog breeds, `DogBuilder` can be kept abstract too. – Péter Török Mar 11 '12 at 16:17
  • 1
    Unfortunately i want to construct 'DogBuilder' as well. – harrybvp Mar 11 '12 at 16:35
  • 1
    is there some way where `ZombiePoodle extends Poodle` and this work: `ZombiePoodle zp = new ZombiePoodle().sleep();` without overriding `sleep()` ? So, a concrete extending another. – Aquarius Power Sep 20 '16 at 22:42
  • 1
    I found it: `Poodle

    > extends Dog

    `, `ZombiePoodle extends Poodle`

    – Aquarius Power Sep 21 '16 at 02:38
  • @AquariusPower: Great, but now how does one `new Poodle()` now ? Cannot do `new Poodle()`. – Bertie Sep 05 '17 at 04:56
  • 1
    @bertie I just did `Poodle p = new Poodle();` and it worked, here the code, but will be a bit hard to read: `public class TestPoodle { static class Poodle

    {public void tstSleep(){};} static class ZombiePoodle

    extends Poodle

    {} static class UltraZombiePoodle

    extends ZombiePoodle

    {} public static void main(String[] args) { Poodle p = new Poodle(); p.tstSleep(); UltraZombiePoodle uzp = new UltraZombiePoodle(); uzp.tstSleep(); } } `

    – Aquarius Power Sep 05 '17 at 05:16
2

I don't believe you can use the getThis trick for multiple levels of inheritance. You have the super class, Pet<T extends Pet<T>>, the first subclass, Dog extends Pet<Dog>, and the second subclass Poodle extends Dog. With the getThis trick, you have the method protected T getThis() and methods like public T rollOver(). This means that both Poodle and Dog have the methods protected Dog getThis() and public Dog rollOver().

I would follow Michael Myers' suggestion to use covariant return types.

Community
  • 1
  • 1
Jeffrey
  • 44,417
  • 8
  • 90
  • 141