Builderパターンについて

デザインパターンのうちのBuilderパターンを勉強したのでメモしておく。

Builderパターンとは

Builderとは、建築者や建築業者などを意味する単語である。
このパターンは複数のコンストラクタを用いて初期化を行いたいような場合に複雑さを抑えることが出来る。

Builderパターンを使わない場合の初期化処理

伝統的には、複数のコンストラクタで初期化を行うときには以下のように記述する。

public class Foo {
    private int a;
    private int b;
    private int c;
    public Foo(a) {
        //何らかの初期化処理
    }
    public Foo(a, b) {
        //何らかの初期化処理
    }
    public Foo(a, b, c) {
        //何らかの初期化処理
    }
}

これはテレスコーピングコンストラクタパターンと呼ばれ、パラメータが増えれば、それに伴ってコンストラクタも増えていくことになる。たった3個のパラメータでは、テレスコーピングコンストラクタパターンはそれほど悪く見えないが、パラメータの数が増えるとすぐに手に負えなくなる。
テレスコーピングコンストラクタパターンは、機能こそするが、多くのパラメータがある場合にはクライアントのコードを書くのが困難になり、そのコードを読むのは更に困難になる。読み手は、パラメータの値が何を意味するかを考えさせられる上に、意味を知るために注意深くパラメータ数を数えなければならない。

Builderパターンを使った初期化処理

GoFデザインパターンのBuilderパターン

以下はGoFデザインパターンのBuilderパターンである。

class Dog {
    private String name;
    private Integer age;
    private String hobby;
    String hello() {
        //...
    }
    //その他セッターやゲッターが記述されている
    //...
}

class Chihuahua {
    private Builder builder;
    Chihuahua(Builder builder) {
        this.builder = builder;
    }
    
    void construct() {
        builder.name("Chibi");
        builder.age(3);
        builder.hobby("Dance");
    }
}

interface Builder {
    void name(String name);
    void age(Integer age);
    void hobby(String hobby);
    Dog getResult();
}

class DogBuilder implements Builder {
    private Dog dog;
    DogBuilder() {
        this.dog = new Dog();
    }
    
    @Override
    public void name(String name) {
        dog.setName(name);
    }
    
    @Override
    public void age(Integer age) {
        dog.setAge(age);
    }
    
    @Override
    public void hobby(String hobby) {
        dog.setHobby(hobby);
    }
    
    @Override
    public Dog getResult() {
        if (dog.getName() == null || dog.getAge() == null) {
            throw new NullPointerException();
        }
        return this.dog;
    }
}
Builder builder = new DogBuilder();
Chihuahua chihuahua = new Chihuahua(builder);
chihuahua.construct();
builder.getResult().hello();

interfaceや実装など、記述量が多くなるが、Builderの実装クラス(Chihuahua)を切り替えることで、生成するクラスの制御が出来る。

Effective JavaのBuilderパターン

こちらは、書籍 Effective Javaに載っているBuilderパターンである。

public class Dog {
    private final String name;
    private final Integer age;
    private final String hobby;
    public static class Builder {
        //必須パラメータ
        private final String name;
        //オプションパラメータ デフォルト値に初期化
        private Integer age = 0;
        private String hobby = "Run";

        public Builder(String name) {
            this.name = name;
            return this;
        }
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        public Builder hobby(String hobby) {
            this.hobby = hobby;
            return this;
        }
        public Dog build() {
           return new Dog(this);
        }
    }

    private Dog(Builder builder) {
        name = builder.name;
        age = builder.age;
        hobby = builder.hobby;
    }
}
Dog chihuahua = new Dog.Builder("Chibi").age(3).hobby("Dance").build();

Builderのセッターメソッドが自身(this)を返すので、呼び出しを連鎖(メソッドチェーン)することが出来る。最後にbuild()を実行することで、メソッドチェーンの一番最初のnew Dogに対してパラメータをセットしたDogクラスを渡すことが出来る。

GoFのBuilderパターンと比べると、記述量が少なく、クライアントからの呼び出しを綺麗に書くことが出来る。

まとめ

  • GoFのBuiderパターンは記述量が増えるが、頑強なクラス設計を行うことが出来る。
  • Effective JavaのBuilderパターンはメソッドチェーンによって呼び出しを簡潔に記述することが出来る。

jQueryのようにメソッドチェーンで書けたら確かに便利ではあるが、学習コストもそれなりにかかるので、メソッドチェーンを使用する場面は考えたほうが良さそうである。

参考
書籍 Effective Java
Javaで書くBuilderパターンのパターン – Qiita
7. Builder パターン | TECHSCORE(テックスコア)
10年間Javaを書いていた僕が Effective Java 第2版を読み返して新人に薦められるのかを考えてみた | susumuis Info