Builderパターンとは
コンストラクタに対して数多くのパラメータをセットする必要がある時に、代わりに使うことが推奨されている実装方法。
ただしこれが推奨されるのは、コンストラクタに設定されるパラメータの内必須のものが少数である時に限られそうです。
理由は、実際に実装方法を見てから振り返って見ます。
様々なBuilderパターン
メジャーな実装方法に以下の二つがあります。それぞれ実装方法を見ていきます。
- Effective Java
- GoF
そもそもBuilderパターンを使わない時のコンストラクタ
class People {
String name;
String sex;
int age;
People(String name, String sex, int age) {
if (name == null || sex == null) throw new NullPointerException();
this.name = name;
this.sex = sex;
this.age = age;
}
}
Builderを使いたくなる理由
- 引数が多くなると、順番を正確に覚えておくのが難しくなる。
- つまり引数の渡し間違いが発生しやすくなる
- 任意の値があると、適当な値をわざわざ渡す記述を書く必要がある。
- つまり記述量が無駄に多くなる
- ↑の例でいうと、
age
は任意の値です。任意だけど、コンストラクタに引数として与えなければならない以上記述はしないといけないですね。無駄なことです。
Effective Java
class People {
String name;
String sex;
int age;
static class Builder {
String name;
String sex;
int age;
Builder(String name, String sex) {
if (name == null || sex == null) throw new NullPointerException();
this.name = name;
this.sex = sex;
}
Builder age(int age) {
this.age = age;
return this;
}
People build() {
if (name == null || sex == null) throw new NullPointerException();
return new People(this);
}
}
}
↑を使うクライアントのコードは↓になります。
People person = new People.Builder("Test", "male").age(100).build();
People person = new People.Builder("Test", "male").build();
任意のフィールドへの値の設定記述が、対象のフィールドを明確にできているのでわかりやすいです。
さらに、必須フィールドにのみ値を設定することもできるので余計な記述を減らせています。
GoF
class People {
String name;
String hobby;
int age;
...
}
class Director {
private Builder builder;
Director(Builder builder) {
this.builder = builder;
}
void construct() {
builder.name("Test");
builder.age(100);
builder.hobby("Tennis");
}
}
interface Builder {
void name(String name);
void hobby(String hobby);
void age(int age);
Object 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 hobby(String hobby) {
people.setHobby(hobby):
}
@Override
public void age(int age) {
people.setAge(age):
}
@Override
public Object getResult() {
if (people.getName() == null || people.getHobby() == null) {
throw new NullPointerException();
}
return this.people;
}
}
↑を使うクライアントのコードは以下のようになります。
Builder builder = new PeopleBuilder();
Director director = new Director(builder);
director.construct();
People people = (People) builder.getResult();
こっちのBuilderパターンは、同じ方法でオブジェクトをなんども作成する時に便利そうです。
Director#construct()
でどのようにオブジェクトを作成するかが定められているためです。
記述量が多くなってしまうのが特徴ですね。
Builderパターンはどんな時に使えそうか?
やはりオブジェクトに与えるパラメータが以下の時には、あまりBuilderパターンは使えそうにないです。
- とても多い
- 全て必須
EffectiveJavaのパターンだと、必須パラメータをXXX.Builder()のコンストラクタ作成時に全て渡さなければならないことに変わりないです。
GoFのパターンだと、毎回決まったオブジェクトが作成されてしまうので、ユーザの情報によってオブジェクトのフィールド値を変更したい時は使えそうにないです。
ただ、引数がたくさんあるが、任意のものが多い時。同じオブジェクトを複数作成したい時はBuilderパターンが使えそうです。