2

First of all, I'm relatively new to Java, so may be what I am asking is trivial, but I could not find an answer here or in other place.

For simplicity, let's assume I have the following class hierarchy:

class Shape {
    protected Shape(double x, double y) {...}
}

class Circle extends Shape {
    public Circle(double radius) {...}
}

class Rectangle extends Shape {
    public Rectangle(double edge) {...}
}

I'd like to use a builder pattern for each shape. So I added Builders to each one of them:

class Shape {
    protected static abstract class BuilderBase<T extends BuilderBase<T, S>, S extends Shape> {
        public T setLocation(double x, double y) {
            // ...
            return (T)this; // ? - is there a way to avoid this casting?
        }

        public abstract S build();
    }

    protected Shape(/*...*/) {/*...*/}
}

class Circle extends Shape {
    public static class Builder extends BuilderBase<Builder, Circle> {
        public Builder setRadius(double radius) {
            //...
            return this;
        }

        @Override
        public Circle build() { return new Circle(/*...*/); }
    }

    private Circle(/*...*/) {/*...*/}
}

class Rectangle extends Shape {
    public static class Builder extends BuilderBase<Builder, Rectangle> {
        public Builder setRadius(double radius) {
            //...
            return this;
        }

        @Override
        public Rectangle build() { 
            return new Rectangle(/*...*/); 
        }
    }

    public Rectangle(/*...*/) {/*...*/}
}

EDIT: Generic here was used in order to allow using Builder methods in any order. For example, in order to allow the following call:

new Circle.Builder()
    .setLocation(0, 0)
    .setRadius(10)
    .build();

My problem is in this casting:

public T setLocation(double x, double y) {
    // ...
    return (T)this; // ? - is there a way to avoid this casting?
}

I'm struggling to find a way to avoid this casting. The only way I found till now was to add yet another abstract method to the BaseBuilder method:

protected static abstract class BuilderBase<T extends BuilderBase<T, S>, S extends Shape> {
    protected abstract T getBuilder();

    public T setLocation(double x, double y) {
        // ...
        return getBuilder();
    }

    //...
}

So each derived builder will have to implement it:

@Override
protected Circle getBuilder() { 
    return this; 
}

It seems to me as overkill but I don't want to get compilation warnings.

Question: is there any more elegant way to avoid the casting?

Denis Itskovich
  • 4,383
  • 3
  • 32
  • 53

1 Answers1

6

You can’t avoid having a little bit of additional code per subclass but you can avoid having an unchecked cast. Just create an abstract method responsible for returning an appropriately typed this. This method has to be implemented by concrete subclasses once but can be used for all methods of the base class which ought to return this:

class Shape {
  protected static abstract
  class BuilderBase<T extends BuilderBase<T, S>, S extends Shape> {
    /** all subclasses should implement {@code self()} as {@code return this;} */
    abstract T self();
    public T setLocation(double x, double y) {
        // ...
        return self();
    }
    public T setFooBar(FooBar x) {
      // ...
      return self();// the more methods you have the more useful self() becomes
  }

    public abstract S build();
  }

  protected Shape(/*...*/) {/*...*/}
}

class Circle extends Shape {
  public static class Builder extends BuilderBase<Builder, Circle> {
    @Override final Builder self() { return this; }

    public Builder setRadius(double radius) {
        //...
        return this;
    }

    @Override
    public Circle build() { return new Circle(/*...*/); }
  }

  private Circle(/*...*/) {/*...*/}
}

class Rectangle extends Shape {
  public static class Builder extends BuilderBase<Builder, Rectangle> {
    @Override final Builder self() { return this; }

    public Builder setRadius(double radius) {
      //...
      return this;
    }

    @Override
    public Rectangle build() { 
      return new Rectangle(/*...*/); 
    }
  }

  public Rectangle(/*...*/) {/*...*/}
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • @Arkadiy: An *unchecked* cast vs. some trivial, easy to prove correct, methods. There *are* developers preferring a bit more but easier to maintain code over lesser but harder to maintain code. From a performance point of view the difference is irrelevant. – Holger Dec 17 '14 at 17:41
  • I have to admit that @Holger's way is safer as it catches the situations like `class ScrewedUpBuilder extends BuilderBase`. –  Dec 17 '14 at 18:33
  • Then again, the use of ScrewedUpBuilder would catch this problem compile-time: you look to chain a ScrewedUpBuilder method and the compiler tells you that it does not exist in CircleBuilder. –  Dec 17 '14 at 18:37
  • @Holger, Actually, your solution is the same as appears in the question itself. Just in the question it is called as `getBuilder()` instead of `self()`. Although, I must say that name `self()` is much more fitting here. I just was wonder whether there is simpler way of doing this – Denis Itskovich Dec 17 '14 at 19:06
  • @Denis Itskovich: I didn’t notice your solution in the question when I wrote my answer. Well, the answer is that Java Generics don’t have a way to declare such a relationship between the type of `this` and a type parameter. Even the core classes suffer from that, e.g. `Comparable`— if there was a way to declare that `T` ought to be the actual type implementing `Comparable`, i.e. its comparable to itself, it was declared using that way… – Holger Dec 17 '14 at 19:51
  • @Holger what is the problem regarding an unchecked cast? Any scenario? – Weishi Z Jul 22 '16 at 23:09
  • @Weishi Zeng: an unchecked cast may be fine or even unavoidable in some scenarios, but you won’t notice, if you are wrong as that’s why these casts are called *unchecked*. So if there is a simple way to avoid it, you should use it. If the solution is not that simple, well, then it’s a trade-off. Here, it was the actual question, how to avoid it. – Holger Aug 15 '16 at 17:31