まくまくJavaノート
数多くのコンストラクタパラメータに直面したときにはビルダーを検討する
2016-02-05

コンストラクタで多数のパラメータを受け取る必要がある場合、パラメータの組み合わせによって、多くのコンストラクタを作成しなくてはいけなくなることがあります。 多くのコンストラクタをメンテナンスするのが大変になるだけでなく、コンストラクタの呼び出し側のコードを見たときに、各パラメータが何を表しているかが分かりにくいという問題が発生します。

// パラメータの意味が読み取れない
StreamInfo info1 = new StreamInfo(200, 150, true, false);
StreamInfo info2 = new StreamInfo(200, 150, false);

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

// JavaBeans パターン
StreamInfo info = new StreamInfo();
info.setWidth(200);
info.setHeight(150);
info.setCaption(true);
info.setProgressive(false);

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

そこで、Builder パターンです。 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 オブジェクトは使いまわすことができるため、フィールドの一部だけが異なるオブジェクトを生成したい場合にも活用できます。

Builder パターンの欠点としては、オブジェクト生成時に各フィールドのコピーを行うために、多少のパフォーマンス低下を招くということが挙げられます。 ただ、マルチスレッドプログラミングにおいて、Immutable なオブジェクトは排他処理なしでアクセスできるという大きな利点を持ちます。 結果として、速度的にも有利になることが多いでしょう。

2016-02-05