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