段階的コンストラクターパターン
静的ファクトリとコンストラクターはどちらも、パラメータが多い場合に適切に対応することが難しい。例えば、クラスのフィールドが6つあり、パラメータを2つ、3つ…など、コンストラクターを分けて作成したい場合は、以下のように段階的コンストラクターパターンを使用することができる。
しかし、これでもパラメータが多くなると、コードを読む際に各値の意味が分からなくなったり、同じ型の複数のパラメータを混同して値を入れてしまう可能性がある。
JavaBeansパターン
JavaBeansパターンは、パラメータのないコンストラクターでオブジェクトを作成し、setterメソッドを呼び出して目的のパラメータの値を設定する方法である。
JavaBeansパターンは、パラメータが増えても値を間違えずにインスタンスを作成できる。しかし、オブジェクトを1つ作成するにはsetterメソッドを複数回呼び出す必要があり、オブジェクトが完全に完成するまでは一貫性が失われる。そのため、クラスを不変にすることはできない。
ビルダーパターン
段階的コンストラクターパターンの安定性とJavaBeansパターンの可読性を兼ね備えたビルダーパターンが主に使用される。
クライアントは、必要なオブジェクトを直接作成するのではなく、必須パラメータのみでコンストラクターを呼び出してビルダーオブジェクトを取得する。その後、ビルダーオブジェクトが提供する一種のsetterメソッドで目的のオプションパラメータを設定する。最後に、パラメータのないbuild()メソッドを呼び出して、必要なオブジェクトを取得する。
Builderクラス内のコンストラクターは必須パラメータのみを受け取り、残りのオプションパラメータは一種のsetterメソッドで埋められる。そして、最後にbuild()メソッドで完成したNutritionFactsWithBuilderPatternオブジェクトを作成する。NutritionFactsWithBuilderPatternクラスは不変であり、ビルダーのsetterメソッドはビルダー自身を返すため、連鎖的に呼び出すことができる。このような方式をFluent APIまたはメソッドチェーンという。
クライアントの立場では、ビルダーパターンによってコードを書きやすく、読みやすく作成することができる。
階層的に設計されたクラスと相性の良いビルダーパターン
Pizza.Builderクラスは、再帰的な型限定を利用するジェネリック型であり、抽象メソッドself()を追加することで、サブクラスでは型変換することなくメソッドチェーンをサポートしている。サブクラスでは、この抽象メソッドの戻り値を自分自身にすればよい。
では、Pizzaのサブクラスであるニューヨークピザとカルツォーネピザを見て、ビルダーパターンの柔軟性を体験してみよう。
各サブクラスのビルダーで定義されたbuild()クラスは、具体的なサブクラスを返している。サブクラスのメソッドが、スーパークラスのメソッドが返した型ではなく、そのサブタイプを返す機能を共変戻り値型付けという。この機能を使用すると、クライアントは型変換を行う必要がなくなる。
クライアントの立場では、Pizzaのenumと各サブクラスのenumを混在させることができ、それぞれ適切なメソッドでオブジェクトを完成させることができる。
ビルダーパターンの欠点
- ビルダーオブジェクトを作成する必要がある。
- コードが冗長になる。
まとめ
コンストラクターまたは静的ファクトリメソッドが処理する必要のあるパラメータが多い場合は、ビルダーパターンを検討してみましょう。
出典