はじめに
ソフトウェア設計においてオブジェクトの生成方法やプロセスを柔軟に管理するための生成パターン、Builder(建築家)パターンについて解説します。このパターンは、複数のステップを経て複雑なオブジェクトを構築する場合に非常に有効です。
Builderパターンとは
Builderパターンは、複雑なオブジェクトの生成とその表現を分離することを目的としたデザインパターンです。オブジェクトの生成プロセスが固定されているのに対し、生成されるオブジェクトのタイプや内容が異なる場合に特に有用です。
パターンの構造
Builderパターンは、以下の主要な構成要素から成ります:
- Builder(建築家): 生成されるオブジェクトの部品を生成および組み立てるためのインターフェースを定義します。
- ConcreteBuilder(具体的な建築家): Builderインターフェースの実装を提供し、具体的なオブジェクトの生成を実行します。
- Director(監督): Builderインターフェースを使用してオブジェクトを組み立てる。
- Product(製品): 生成される複雑なオブジェクトを表します。
パターンの適用
Builderパターンは、以下のような状況で特に有用です:
- オブジェクトが複数の部分から成る場合。
- オブジェクトの生成プロセスが他のオブジェクトとは異なるステップを必要とする場合。
例: あるドキュメントフォーマット(HTML、Markdown、PDFなど)に対応したコンテンツ生成ツールを考えると、Builderパターンは異なるフォーマットのドキュメントを同じ手順で生成するのに適しています。
パターンの利点と欠点
利点
- 柔軟性: 同じ生成プロセスで異なるタイプのオブジェクトを生成できます。
- クリアな役割分担: 複雑なオブジェクトの生成とその表現が分離され、コードの独立性と再利用性が向上します。
欠点
- コードの複雑性: シンプルなオブジェクトの生成の場合、Builderパターンは必要以上にコードの複雑性を増加させる可能性があります。
実例
以下にJavaとKotlinでのBuilderパターンの実装例を示します。
Java
class Meal {
private String drink;
private String mainCourse;
public void setDrink(String drink) {
this.drink = drink;
}
public void setMainCourse(String mainCourse) {
this.mainCourse = mainCourse;
}
@Override
public String toString() {
return "Meal [drink=" + drink + ", mainCourse=" + mainCourse + "]";
}
}
// Builder
interface MealBuilder {
void buildDrink();
void buildMainCourse();
Meal getMeal();
}
// ConcreteBuilder
class VegetarianMealBuilder implements MealBuilder {
private Meal meal = new Meal();
public void buildDrink() {
meal.setDrink("Water");
}
public void buildMainCourse() {
meal.setMainCourse("Vegetarian Pasta");
}
public Meal getMeal() {
return meal;
}
}
class NonVegetarianMealBuilder implements MealBuilder {
private Meal meal = new Meal();
public void buildDrink() {
meal.setDrink("Soda");
}
public void buildMainCourse() {
meal.setMainCourse("Steak");
}
public Meal getMeal() {
return meal;
}
}
// Director
class MealDirector {
private MealBuilder mealBuilder;
public MealDirector(MealBuilder mealBuilder) {
this.mealBuilder = mealBuilder;
}
public void constructMeal() {
mealBuilder.buildDrink();
mealBuilder.buildMainCourse();
}
public Meal getMeal() {
return mealBuilder.getMeal();
}
}
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder vegetarianMealBuilder = new VegetarianMealBuilder();
MealDirector vegetarianMealDirector = new MealDirector(vegetarianMealBuilder);
vegetarianMealDirector.constructMeal();
System.out.println(vegetarianMealDirector.getMeal().toString());
MealBuilder nonVegetarianMealBuilder = new NonVegetarianMealBuilder();
MealDirector nonVegetarianMealDirector = new MealDirector(nonVegetarianMealBuilder);
nonVegetarianMealDirector.constructMeal();
System.out.println(nonVegetarianMealDirector.getMeal().toString());
}
}
Kotlin
data class Meal(var drink: String = "", var mainCourse: String = "")
// Builder
interface MealBuilder {
fun buildDrink(): String
fun buildMainCourse(): String
fun getMeal(): Meal
}
// ConcreteBuilder
class VegetarianMealBuilder : MealBuilder {
private val meal = Meal()
override fun buildDrink(): String {
meal.drink = "Water"
return meal.drink
}
override fun buildMainCourse(): String {
meal.mainCourse = "Vegetarian Pasta"
return meal.mainCourse
}
override fun getMeal(): Meal {
return meal
}
}
class NonVegetarianMealBuilder : MealBuilder {
private val meal = Meal()
override fun buildDrink(): String {
meal.drink = "Soda"
return meal.drink
}
override fun buildMainCourse(): String {
meal.mainCourse = "Steak"
return meal.mainCourse
}
override fun getMeal(): Meal {
return meal
}
}
// Director
class MealDirector(private val mealBuilder: MealBuilder) {
fun constructMeal() {
mealBuilder.buildDrink()
mealBuilder.buildMainCourse()
}
fun getMeal(): Meal {
return mealBuilder.getMeal()
}
}
fun main() {
val vegetarianMealBuilder = VegetarianMealBuilder()
val vegetarianMealDirector = MealDirector(vegetarianMealBuilder)
vegetarianMealDirector.constructMeal()
println(vegetarianMealDirector.getMeal())
val nonVegetarianMealBuilder = NonVegetarianMealBuilder()
val nonVegetarianMealDirector = MealDirector(nonVegetarianMealBuilder)
nonVegetarianMealDirector.constructMeal()
println(nonVegetarianMealDirector.getMeal())
}
この例では、Meal
という製品を生成するためのMealBuilder
インターフェースを持っています。VegetarianMealBuilder
とNonVegetarianMealBuilder
は具体的な建築家として、MealDirector
を使用して実際のMeal
製品を生成します。
まとめ
Builderパターンは、複雑なオブジェクトの生成プロセスとその表現を分離することで、同じ生成手順で異なるタイプのオブジェクトを柔軟に生成することができるデザインパターンです。ただし、不適切に使用するとコードの複雑性が増すため、適切な状況での利用が鍵となります。
次回は、Prototypeパターンについて解説します。