<一覧に戻る

ビルダーパターン

コンストラクタの引数が多すぎて、どれが必須でどれが任意なのか迷ったことはありませんか?

たとえば「車」を表すオブジェクトを作るとき、色やエンジン、タイプなど設定項目が増えると、毎回長い引数リストを書いたり、引数の順番を間違えたりしがちですよね。 そんな「複雑なオブジェクトの組み立て」をわかりやすく、ミスなく進められるのが、Pythonでも使えるデザインパターンの1つビルダーパターン(Builder Pattern)です。

ビルダーパターンは、オブジェクトを「どのように作るか」という手順をクラス本体から切り離します。これにより、同じ組み立てプロセスでも、設定を変えるだけで違うバリエーションのオブジェクトを簡単に作れるようになります。

初学者の方でも安心して読み進められるよう、実例とともに丁寧に解説します。

ビルダーパターンのメリット

まず、ビルダーパターンを使うと何が嬉しいのか、全体像を把握しておきましょう。

  • 可読性の向上: オブジェクトの組み立て手順を「段階的に」書けるため、コードを読んだ人が意図を理解しやすくなります。
  • 柔軟性: 必要なオプションだけを順不同で設定でき、違う構成のオブジェクトを同じ手順で量産できます。
  • 安全性: 「必須項目が未設定ならエラーにする」といった検証を build タイミングに集中させられます。
  • 再利用性: 組み立て手順を共通化できるので、同じロジックを繰り返し使い回せます。
  • メンテナンス性: 新しい項目が増えても、既存コードに大きな影響を与えずに拡張できます。

引数だらけのコンストラクタから卒業して、読みやすく変更に強いコードへ。そんなイメージがビルダーパターンです。

サンプルコードでビルダーパターンを理解する。

ここでは、車両を表す Vehicle を例にします。 車両タイプ(Car/Truck など)、色、エンジン種別を持つオブジェクトを、ビルダーパターンで安全かつ読みやすく組み立てます。

ステップ1: Vehicle クラスの定義

はじめに、完成品となる Vehicle クラスを用意します。これは「組み立て済み」のオブジェクトを表すだけのシンプルなクラスです。

class Vehicle:
    def __init__(self, vehicle_type: str, color: str, engine_type: str) -> None:
        self.vehicle_type = vehicle_type
        self.color = color
        self.engine_type = engine_type

    def __str__(self) -> str:
        return f"{self.color} {self.vehicle_type} with {self.engine_type} engine"

このクラスは、車両タイプ・色・エンジン種別という3つの属性を持ちます。画面にわかりやすく表示できるよう、__str__ を実装しています。

ここでは「完成したらどう見えるか」にだけ集中し、作り方の手順は一切書きません。組み立てのロジックは、このあと登場するビルダー側に移します。

ステップ2: VehicleBuilder クラスの定義

次に、Vehicle を「どう組み立てるか」を担当するビルダーを作ります。使いやすくするため、メソッドチェーン(メソッドをドットでつなぐ書き方)に対応させます。

#上記のコードの続き
from typing import Optional

class VehicleBuilder:
    """
    Vehicle オブジェクトを段階的に組み立てるためのビルダー。
    必須項目の検証やデフォルト値の設定もここで行います。
    """

    def __init__(self) -> None:
        self._vehicle_type: Optional[str] = None   # 必須
        self._color: str = "White"                 # 任意(デフォルト: White)
        self._engine_type: Optional[str] = None    # 必須

    def set_vehicle_type(self, vehicle_type: str) -> "VehicleBuilder":
        self._vehicle_type = vehicle_type
        return self  # メソッドチェーンを可能にするため self を返す

    def set_color(self, color: str) -> "VehicleBuilder":
        self._color = color
        return self

    def set_engine_type(self, engine_type: str) -> "VehicleBuilder":
        self._engine_type = engine_type
        return self

    def build(self) -> Vehicle:
        # 必須項目のチェック。満たさない場合はエラーにして早期に気づけるようにする。
        if not self._vehicle_type:
            raise ValueError("vehicle_type は必須です。例: 'Car', 'Truck'")
        if not self._engine_type:
            raise ValueError("engine_type は必須です。例: 'V6', 'V8', 'EV'")

        # ここで初めて完成品の Vehicle を返す
        return Vehicle(
            vehicle_type=self._vehicle_type,
            color=self._color,
            engine_type=self._engine_type,
        )

このビルダーは、完成品を作るための中間的な保管場所として機能します。set_* メソッドで属性を1つずつ設定し、各メソッドは self を返すことで「.set_xxx(...).set_yyy(...).build()」という流れを自然に書けます。

build() では未設定の必須項目がないかを検証し、問題がなければ Vehicle を生成して返します。色はデフォルトで "White" にしているため、設定を省略しても動作します。「任意の値はデフォルト」「必要な値はチェック」と役割が明確になるのがポイントです。

ステップ3: VehicleBuilder の使用例

最後に、実際にビルダーを使ってオブジェクトを作ってみましょう。違う設定でも同じ流れで組み立てられることが実感できます。

#上記のコードの続き

# すべての項目を明示して作成
car = (
     VehicleBuilder()
    .set_vehicle_type("Car")
    .set_color("Red")
    .set_engine_type("V6")
    .build()
)

# 色はデフォルト(White)のまま作成
truck = (
    VehicleBuilder()
    .set_vehicle_type("Truck")
    .set_engine_type("V8")
    .build()
)

print(car)   # Red Car with V6 engine
print(truck) # White Truck with V8 engine

この例では、同じ「組み立ての流れ」を保ちながら、設定だけを変えて「赤い車」と「白いトラック」をそれぞれ作っています。メソッドチェーンのおかげで、設定の意図が読み取りやすく、引数の順番を間違える心配もありません。必要なパラメータを後から追加しても、既存コードの読みやすさを保ちやすいのが魅力です。

いつビルダーパターンを使うと便利?

いつビルダーパターンを使うと便利なのでしょうか?以下のような状況になったらビルダーパターンを検討してみましょう。

  • 引数が多いコンストラクタに違和感があるとき(順番ミスや可読性低下が気になる)。
  • オプション項目が多く、段階的に設定したいとき(後から少しずつ埋めたい)。
  • 必須項目のチェックやデフォルト値の設定を1カ所に集約したいとき。
  • 同じ構築手順で、色違い・仕様違いなどのバリエーションを量産したいとき。

もし「このオブジェクト、設定項目が増えてきたな…」と感じたら、ビルダーパターンに切り替える合図かもしれません。

まとめ

Python で複雑なオブジェクトを安全かつ読みやすく組み立てたいなら、ビルダーパターンは強力な選択肢です。 組み立て手順をクラスから切り離し、必須チェックやデフォルト設定を一元化することで、コードの可読性・柔軟性・再利用性が大きく向上します。引数だらけのコンストラクタに悩んでいるなら、まずは今回の Vehicle の例を真似して、小さなビルダーから試してみましょう!

慣れてくると、実務でも「設定が多いオブジェクト」を扱う場面で頼れる相棒になってくれます。

出力結果: