Effective Java - Item 2. コンストラクタにパラメータが多い場合はビルダーを検討する

コンストラクタにパラメータが多い場合は、ビルダーパターンを利用する方が便利である。

コンストラクタを使用した初期化

constructorで多くのパラメータを受け取る必要がある場合、パラメータの組み合わせによって多くのconstructorを書かなければならないことがある。多くのコンストラクタを保守するのが難しいだけでなく、コンストラクタ呼び出し側のコードを見ると各パラメータが何を表しているのか分かりにくいという問題が発生する。

// 페라미터의 의미가 알 수가 없다
StreamInfo info1 = new StreamInfo(200, 150, true, false);
StreamInfo info2 = new StreamInfo(200, 150, false);

JavaBeans Patternを使用した初期化

複数フィールドの初期化方法としては、constructorではパラメータを受け取らず、インスタンスを生成した後に複数のsetterを呼び出してフィールドを設定する方法も考えられる。

// JavaBeans 패턴
StreamInfo info = new StreamInfo();
info.setWidth(200);
info.setHeight(150);
info.setCaption(true);
info.setProgressive(false);

この方法でフィールドの値を設定すると、どの値がどのフィールドに設定されているかが明確になる。しかし、setterを提供するということは、そのオブジェクトを不変(Immutable)にできないことを意味しており、マルチスレッド設計において安全に扱えるクラスを作るのが難しくなる。

Builder Patternを使用した初期化

そこでビルダーパターンである。Builderパターンでは、インスタンス生成用のパラメータを保持するBuilderオブジェクトを提供し、必ずこのオブジェクトを使って生成したいオブジェクトを生成するようにする。Builderクラスには名前付きのsetterメソッドを提供するため、各パラメータを設定するコードが分かりやすくなる。

// Builder 패턴으로 생성할 수 있도록 한 StreamInfo 클래스
public class StreamInfo {
    private final int width;
    private final int height;
    private final boolean hasCaption;
    private final boolean isProgressive;

    /** To create an instance of StreamInfo, use Builder#build() instead. */
    private StreamInfo(Builder builder) {
        width = builder.width;
        height = builder.height;
        hasCaption = builder.hasCaption;
        isProgressive = builder.isProgressive;
    }

    public int getWidth() { return width; }
    public int getHeight() { return height; }
    public boolean hasCaption() { return hasCaption; }
    public boolean isProgressive() { return isProgressive; }

    public static class Builder {
        private int width;
        private int height;
        private boolean hasCaption;
        private boolean isProgressive;

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

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

        public Builder setCaption(boolean hasCaption) {
            this.hasCaption = hasCaption;
            return this;
        }

        public Builder setProgressive(boolean isProgressive) {
            this.isProgressive = isProgressive;
            return this;
        }

        public StreamInfo build() {
            // If necessary fields have not been set yet,
            // IllegalStateException can be thrown here.
            return new StreamInfo(this);
        }
    }
}

Builderオブジェクトを使ったインスタンス生成例は次のとおりである。

StreamInfo info = new StreamInfo.Builder().setWidth(200).setHeight(150)
        .setCaption(true).setProgressive(false).build();

生成されたStreamInfoオブジェクトにはsetterがないため、Immutableであり、スレッドから安全である。Builderオブジェクトは再利用可能なため、フィールドの一部だけが異なるオブジェクトを生成したい場合にも活用できる。

ビルダーパターンの欠点は、オブジェクト生成時に各フィールドをコピーするため、わずかな性能低下を招くことである。ただし、マルチスレッドプログラミングにおいて、Immutableなオブジェクトは排他制御なしにアクセスできるという大きな利点を持つ。その結果、速度面でも有利になる場合が多い。