第08回: Factoryパターン — 「どのオブジェクトを作るか」を専門クラスに任せる

章: 第1章: オブジェクト指向の深化

new PaymentClass() を呼び出し元に書き続けていませんか?

支払い方法が増えるたびに、コントローラや処理クラスで if ($type === 'credit') { $payment = new CreditPayment(); } を書き続けていませんか?

生成ロジックが複数箇所に散らばると、新しい種類を追加したときにどこを変えればいいか分からなくなります

Factoryパターンは「オブジェクトの生成ロジック」を専用クラスに集めることで、呼び出し元をシンプルに保ちます。

Factoryパターンの実装


<?php
interface Payment {}
class CreditPayment implements Payment {}
class BankPayment  implements Payment {}
class ConveniPayment implements Payment {}

// Factoryクラス:生成ロジックをここに集める
class PaymentFactory {
    public static function create(string $type): Payment {
        return match ($type) {
            'credit' => new CreditPayment(),
            'bank'   => new BankPayment(),
            'conve'  => new ConveniPayment(),
            default  => throw new InvalidArgumentException("Unknown type: {$type}"),
        };
    }
}

// 呼び出し元は生成の詳細を知らなくていい
$payment = PaymentFactory::create('credit');

直接 new vs Factoryパターン

観点 直接 new Factoryパターン
新しい種類の追加 全ての呼び出し箇所を変更 Factoryクラスだけ変更
生成ロジックの複雑化 呼び出し元が肥大化 Factory内に封じ込められる
生成の一元管理 複数箇所に分散 1箇所に集約

チェックポイント: 「同じ match($type)if($type) が複数の場所に書かれている」と気づいたら、Factoryクラスへの集約タイミングです。

DIコンテナとFactoryの組み合わせ


<?php
// DIコンテナでFactoryを注入する場合
class OrderController {
    public function __construct(private PaymentFactory $factory) {}

    public function checkout(string $paymentType): void {
        $payment = $this->factory->create($paymentType);
        // 以降は$paymentのインターフェイスだけを使う
    }
}

Factoryの種類

  • 静的Factoryメソッド: シンプルな生成ロジックに向く(上記の例)
  • Factoryクラス(非静的): テストでモックに差し替えたいとき
  • Abstract Factory: 関連するオブジェクト群をまとめて生成するとき

チェックポイント: テスト可能性を重視する場合は静的メソッドよりクラスインスタンスとして注入する形を選びましょう。

まとめ & 次のステップ

  • Factoryパターンはオブジェクトの生成ロジックを専用クラスに分離する
  • 新しい種類の追加時、Factoryだけを変更すれば呼び出し元は無変更で済む
  • DIコンテナと組み合わせるとテスタビリティも確保できる

次回はDTOとValue Objectを学びます。「データをオブジェクトとして表現する」ことで、型安全性と可読性を高める設計手法です。

Related Articles