1. Qiita
  2. 投稿
  3. デザインパターン

Javaで書くBuilderパターンのパターン

  • 226
    いいね
  • 0
    コメント

Builderパターン

個人的にBuilderパターンはオブジェクトの生成制御や、ものによっては可読性が高くて好きなパターンなんですが、その実装には用途によっていくつかパターンがあるので、まとめてみました。

生成するオブジェクトの条件

  • クラス名:People
  • フィールド:String name(必須), Integer age(必須), String hobby(オプション)
  • 必須要素はnullを禁止
  • PeopleクラスはStringを返り値とするhelloメソッドを持つ
  • 今回はBuilderパターンの比較のため、パターン上必要でない限りgetterなどのメソッドは省略

Native Builder

Builderパターンではなく、ただのコンストラクタ。Builderパターンを使いたくなるのはこれをやりたくないからだけど、比較のために記載。

NativeBuilder
class People {
    private String name;
    private Integer age;
    private String hobby;

    People(String name, Integer age, String hobby) {
        if (name == null || age == null) {
            throw new NullPointerException();
        }
        this.name = name;
        this.age = age;
        this.hobby = hobby;
    }

    // getter,setter...
    String hello() {
        //...
    }
}

// new People("Tom", 12, "BaseBall").hello();
  • 記述が単純。
  • 不要なオプション要素が増えると、コンストラクタに大量のnullなどを渡す必要がある。
  • 全ての値を引数で渡すため、渡している値が何を表しているか確認しづらく、また間違いやすい。

GoF Builder

GoF本(オブジェクト指向における再利用のためのデザインパターン)で紹介されているBuilderパターン。

GoFBuilder
class People {
    private String name;
    private Integer age;
    private String hobby;

    // getter,setter...
    String hello() {
        //...
    }
}

class Director {
    private Builder builder;

    Director(Builder builder) {
        this.builder = builder;
    }

    void construct() {
        builder.name("Tom");
        builder.age(12);
        builder.hobby("BaseBall");
    }
}

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

    People getResult();
}

class PeopleBuilder implements Builder {
    private People people;

    PeopleBuilder() {
        this.people = new People();
    }

    @Override
    public void name(String name) {
        people.setName(name);
    }
    @Override
    public void age(Integer age) {
        people.setAge(age);
    }
    @Override
    public void hobby(String hobby) {
        people.setHobby(hobby);
    }
    @Override
    public People getResult() {
        if (people.getName() == null || people.getAge() == null) {
            throw new NullPointerException();
        }
        return this.people;
    }
}

// Builder builder = new PeopleBuilder();
// Director director = new Director(builder);
// director.construct();
// builder.getResult().hello();

  • 生成されるオブジェクト(People)と生成ロジック(Builder)が分離され、PeopleがBuilderを知らなくていい。
  • Builderの実装クラスを切り替えることで、生成するPeopleの制御を出来る。
  • interfaceや実装など、記述量が多くなる。
  • 生成処理が他のBuilderと比べると複雑。

Effective Java Builder

Effective Javaで紹介されているBuilderパターン

EffectiveJavaBuilder
class People {
    private String name;
    private Integer age;
    private String hobby;

    static class Builder {
        private String name;
        private Integer age;
        private String hobby;

        Builder(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        Builder hobby(String hobby) {
            this.hobby = hobby;
            return this;
        }

        People build() {
            if (name == null || age == null) {
                throw new NullPointerException();
            }
            return new People(this);
        }
    }

    private People(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.hobby = builder.hobby;
    }

    String hello() {
        //...
    }
}

// new People.Builder("Tom", 12).hobby("BaseBall").build().hello();
  • 流れるインターフェイスでオブジェクトの生成が可能。
  • GoF Builderと比べ少ない記述量でオブジェクトの生成ができる。
  • 生成ロジック(Builder)とオブジェクト(People)の分離ができない。

Lombok Builder

ちょっと変化球ですが、Lombokというライブラリの@Builderアノテーションで作成するパターン。

LombokBuilder
@Builder
class People {
    @NonNull
    private String name;
    @NonNull
    private Integer age;
    private String hobby;

    String hello() {
        //...
    }
}

// People.builder().name("Tom").age(12).hobby("BaseBall").build().hello();
  • 簡潔な記述で実現できる
  • @NotNullで生成時に例外を発生させることで、必須を実現
  • 生成ロジックとオブジェクトの分離ができない(実質ソースには記述されない)

Fluent Builder

Javaによる関数型プログラミングに紹介されているパターン。こちらは実際はBuilderというより、ローンパターン。

FluentBuilder
class People {
    private People() {
    }

    private String name;
    private Integer age;
    private String hobby;

    People name(String name) {
        this.name = name;
        return this;
    }
    People age(Integer age) {
        this.age = age;
        return this;
    }
    People hobby(String hobby) {
        this.hobby = hobby;
        return this;
    }

   static String hello(final Consumer<People> builder) {
        People people = new People();
        builder.accept(people);
        if (people.name == null || people.age == null) {
            throw new NullPointerException();
        }
        //...
    }
}

// Consumer<People> builder = people -> people.name("Tom").age(12).hobby("BaseBall");
// People.hello(builder);
  • Peopleオブジェクトのスコープが制限され、hello実行終了すると参照が消える。
  • 流れるインターフェイスでオブジェクトの生成ロジックが記述できる。
  • 生成ロジック(Consumer<People>)の切り替えが用意に可能。
  • Java8環境でないと、記述量も可読性も悪くなる。

まとめ

JavaでBuilderっていってもいろいろ種類がありますね。個人的には細かな生成の制御をしなくていいなら、Lombok Builderが記述量が少なくて好きです。
Fluent BuilderもLombokを使えば更に簡潔に記載できますし。
他にもいろいろあると思うので、コメントでもなんでももらえればと思います!