Edited at

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

More than 1 year has passed since last update.


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を使えば更に簡潔に記載できますし。

他にもいろいろあると思うので、コメントでもなんでももらえればと思います!