はじめに
冷静に考えれば当たり前のことなんですけど、ちょっと考え込んでしまったのでメモとして……
Builderを作る
Getter
/ Setter
は省略しますが縦と横の長さをフィールドに持つSquare
クラスがあります。
public class Square {
private int width;
private int height;
}
これをBuilder
を使って値を設定できるようにし、最後に面積を返すようなクラスを作ると以下のようになると思います。
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();
}
}
}
すると、こんな感じで面積を求めることができます
public class Main {
public static void main(String[] args) {
SquareBuilder.Builder squareBuilder = new SquareBuilder.Builder();
System.out.println("面積は:" + squareBuilder.width(5).height(10).build());
}
}
ここまでは特に問題はありません。
Builderを継承したBuilderを作る
次にSquare
を継承したCube
クラスを作成します。(Getter
/ Setter
は省略します。)
public class Cube extends Square {
private int depth;
}
高さがdepth
で良かったのかはさておき……
同じくBuilder
も継承します
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();
}
}
}
すると、こんな感じで体積を求めることができます。
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()
を最初に呼ばないといけないのです。
理由は、width()
とheight()
の戻り値はSquareBuilder.Builder
型のため、CubeBuilder.Builder
でしか定義していないdepth()
を呼ぶことができないからです。
ここでジェネリクスを使います。
参考:Subclassing a Java Builder class
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();
}
}
}
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()
を先に呼ぶ必要がなくなります。
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が出るのでちょっと気持ち悪いです。
投稿した経緯
大量のフィールドを持つPOJOに値を入れてJSON形式に出力するプログラムで、いちいちSetter
で値を入れるのが面倒になり、Builder
を活用としてハマったので。
おまけ
今回の例では面積を求められなくなりますが、SquareBuilder
を抽象クラスにすることによってWarningを無くすこともできます。
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();
}
}
}
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();
}
}
}