405
370

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-01-18

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();
  • 簡潔な記述で実現できる
  • @NonNullで生成時に例外を発生させることで、必須を実現
  • 生成ロジックとオブジェクトの分離ができない(実質ソースには記述されない)

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を使えば更に簡潔に記載できますし。
他にもいろいろあると思うので、コメントでもなんでももらえればと思います!

405
370
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
405
370

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?