はじめに
冷静に考えれば当たり前のことなんですけど、ちょっと考え込んでしまったのでメモとして……
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();
}
}
}