Method Chaining: How to use getThis() trick in cas

2020-02-09 14:47发布

问题:

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?

回答1:

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


回答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.