Builderを継承したBuilderを作ってハマったお話

### はじめに

冷静に考えれば当たり前のことなんですけど、ちょっと考え込んでしまったのでメモとして……

### Builderを作る

`Getter` / `Setter`は省略しますが縦と横の長さをフィールドに持つ`Square`クラスがあります。

```java:Square.java
public class Square {
  private int width;
  private int height;
}

```

これを`Builder`を使って値を設定できるようにし、最後に面積を返すようなクラスを作ると以下のようになると思います。

```java:SquareBuilder.java
public class SquareBuilder {

  public static class Builder {

    Square square = new Square();

    public Builder() {}

    public Builder width(int width) {
      square.setWidth(width);
      return this;
    }

    public Builder height(int height) {
      square.setHeight(height);
      return this;
    }

    public int build() {
      return square.getWidth() * square.getHeight();
    }
  }
}
```

すると、こんな感じで面積を求めることができます

```java:Main.java
public class Main {
  public static void main(String[] args) {
    SquareBuilder.Builder squareBuilder = new SquareBuilder.Builder();
    System.out.println("面積は：" + squareBuilder.width(5).height(10).build());
  }
}
```
ここまでは特に問題はありません。:slight_smile:

### Builderを継承したBuilderを作る

次に`Square`を継承した`Cube`クラスを作成します。(`Getter` / `Setter` は省略します。)

```java:Cube.java
public class Cube extends Square {
  private int depth;
}
```

高さが`depth`で良かったのかはさておき……:kissing:
同じく`Builder`も継承します

```java:CubeBuilder.java
public class CubeBuilder extends SquareBuilder {

  public static class Builder extends SquareBuilder.Builder {

    private Cube cube = new Cube();

    public Builder() {}

    public Builder depth(int depth) {
      cube.setDepth(depth);
      return this;
    }

    @Override
    public int build() {
      return super.build() * cube.getDepth();
    }
  }
}
```

すると、こんな感じで体積を求めることができます。

```java:Main.java
public class Main {
  public static void main(String[] args) {
    CubeBuilder.Builder cubeBuilder = new CubeBuilder.Builder();
    System.out.println("体積は：" + cubeBuilder.depth(2).width(5).height(10).build());
  }
}
```

このコードはコンパイルを通りますし体積も求められますが、実は問題が。
体積を求めるには`depth()`を最初に呼ばないといけないのです。:worried:

理由は、`width()`と`height()`の戻り値は`SquareBuilder.Builder`型のため、`CubeBuilder.Builder`でしか定義していない`depth()`を呼ぶことができないからです。

ここでジェネリクスを使います。

参考：[Subclassing a Java Builder class](https://stackoverflow.com/questions/17164375/subclassing-a-java-builder-class)

```java:SquareBuilder.java
public class SquareBuilder {

  public static class Builder<T extends Builder<T>> {

    Square square = new Square();

    public Builder() {}

    public T width(int width) {
      square.setWidth(width);
      return (T) this;
    }

    public T height(int height) {
      square.setHeight(height);
      return (T) this;
    }

    public int build() {
      return square.getWidth() * square.getHeight();
    }
  }
}
```

```java:CubeBuilder.java
public class CubeBuilder extends SquareBuilder {

  public static class Builder extends SquareBuilder.Builder<Builder> {

    private Cube cube = new Cube();

    public Builder() {}

    public Builder depth(int depth) {
      cube.setDepth(depth);
      return this;
    }

    @Override
    public int build() {
      return super.build() * cube.getDepth();
    }
  }
}
```

これにより、`width()`と`height()`の返り値の型が`SquareBuilder.Builder`ではなく`SquareBuilder.Builder`を継承したクラスになるので`depth()`を先に呼ぶ必要がなくなります。

```java:Main.java
public class Main {
  public static void main(String[] args) {
    SquareBuilder.Builder squareBuilder = new SquareBuilder.Builder();
    System.out.println("面積は：" + squareBuilder.width(5).height(10).build());

    CubeBuilder.Builder cubeBuilder = new CubeBuilder.Builder();
    System.out.println("体積は：" + cubeBuilder.width(5).height(10).depth(2).build());
  }
}
```

ですが、Warningが出るのでちょっと気持ち悪いです。:mask:

### 投稿した経緯
大量のフィールドを持つPOJOに値を入れてJSON形式に出力するプログラムで、いちいち`Setter`で値を入れるのが面倒になり、`Builder`を活用としてハマったので。

#### おまけ

今回の例では面積を求められなくなりますが、`SquareBuilder`を抽象クラスにすることによってWarningを無くすこともできます。

```java:SquareBuilder.java
public class SquareBuilder {

  public abstract static class Builder<T extends Builder<T>> {

    Square square = new Square();

    public Builder() {}

    public abstract T getThis();

    public T width(int width) {
      square.setWidth(width);
      return getThis();
    }

    public T height(int height) {
      square.setHeight(height);
      return getThis();
    }

    public int build() {
      return square.getWidth() * square.getHeight();
    }
  }
}
```

```java:CubeBuilder.java
public class CubeBuilder extends SquareBuilder {

  public static class Builder extends SquareBuilder.Builder<Builder> {

    private Cube cube = new Cube();

    public Builder() {}

    @Override
    public Builder getThis() {
      return this;
    }

    public Builder depth(int depth) {
      cube.setDepth(depth);
      return this;
    }

    @Override
    public int build() {
      return super.build() * cube.getDepth();
    }
  }
}
```