🐈‍⬛

PHP デザインパターン学習:Factory Method パターンの実践的な実装と理解

に公開

はじめに

Factory Method パターンは、オブジェクト指向プログラミングにおける重要な設計パターンの一つです。このパターンを理解し適切に実装することで、コードの柔軟性、保守性、拡張性を大幅に向上させることができます。

本記事のサンプルコードの完全版は GitHub リポジトリで公開しています

Factory Method パターンとは

Factory Method パターンは、「オブジェクト生成のためのインターフェースを定義しながら、生成されるオブジェクトの具体的なクラスを決定する責任をサブクラスに委ねる」デザインパターンです。

簡単に言えば、「どのようにオブジェクトを作るか」という問題と「オブジェクトで何をするか」という問題を分離します。

PHP での実装例

以下は、Factory Method パターンを使用した決済処理システムの実装例です。

// 抽象的な決済処理のインターフェース
interface PaymentProcessor {
    public function processPayment($amount);
}

// 具体的な決済処理クラス
class CreditCardProcessor implements PaymentProcessor {
    public function processPayment($amount) {
        echo "クレジットカードで{$amount}円の決済を処理しました。\n";
        return true;
    }
}

class PayPalProcessor implements PaymentProcessor {
    public function processPayment($amount) {
        echo "PayPalで{$amount}円の決済を処理しました。\n";
        return true;
    }
}

class BankTransferProcessor implements PaymentProcessor {
    public function processPayment($amount) {
        echo "銀行振込で{$amount}円の決済を処理しました。\n";
        return true;
    }
}

// 抽象的なファクトリークラス
abstract class PaymentFactory {
    // ファクトリーメソッド
    abstract public function createProcessor(): PaymentProcessor;

    // テンプレートメソッド
    public function processPayment($amount) {
        $processor = $this->createProcessor();
        return $processor->processPayment($amount);
    }
}

// 具体的なファクトリークラス
class CreditCardFactory extends PaymentFactory {
    public function createProcessor(): PaymentProcessor {
        return new CreditCardProcessor();
    }
}

class PayPalFactory extends PaymentFactory {
    public function createProcessor(): PaymentProcessor {
        return new PayPalProcessor();
    }
}

class BankTransferFactory extends PaymentFactory {
    public function createProcessor(): PaymentProcessor {
        return new BankTransferProcessor();
    }
}

// 使用例
function processOrder($paymentType, $amount) {
    $factory = null;

    switch ($paymentType) {
        case 'credit_card':
            $factory = new CreditCardFactory();
            break;
        case 'paypal':
            $factory = new PayPalFactory();
            break;
        case 'bank_transfer':
            $factory = new BankTransferFactory();
            break;
        default:
            throw new Exception("サポートされていない決済方法: {$paymentType}");
    }

    $factory->processPayment($amount);
}

Factory Method パターンのメリット

1. 抽象化によるコードの柔軟性向上

クライアントコードは具体的なクラスではなく、インターフェースに依存するため、実装を容易に切り替えられます。

2. オブジェクト生成ロジックの集中管理

複雑な初期化処理をファクトリー内にカプセル化できます。

// 複雑な初期化が必要な場合も、ファクトリー内部で処理
class CreditCardFactory extends PaymentFactory {
    public function createProcessor(): PaymentProcessor {
        $processor = new CreditCardProcessor();
        $processor->setApiKey('abc123');
        $processor->setEnvironment('production');
        // 他の初期化処理...
        return $processor;
    }
}

3. 条件に基づくオブジェクト生成

特定の条件によって異なるオブジェクトを返すロジックをファクトリー内に隠蔽できます。

4. テスト容易性の向上

テスト時にモックオブジェクトを使用することが容易になります。

// テスト用のファクトリーの実装が簡単
class TestingPaymentFactory extends PaymentFactory {
    public function createProcessor(): PaymentProcessor {
        return new MockPaymentProcessor();
    }
}

5. システムの拡張性向上

新しい機能を追加する際、既存のコードを変更せずに拡張できます。

Q&A: Factory Method パターンに関する疑問

Q: Factory を通さずに直接 Processor を new して実行できないのでしょうか?

当然、直接インスタンス化することは技術的に可能です。例えば:

function processOrder($paymentType, $amount) {
    if ($paymentType == 'credit_card') {
        new CreditCardProcessor()->processPayment($amount);
        return;
    }

    if ($paymentType == 'paypal') {
        new PayPalProcessor()->processPayment($amount);
        return;
    }

    if ($paymentType == 'bank_transfer') {
        new BankTransferProcessor()->processPayment($amount);
        return;
    }
}

しかし、このアプローチには以下のデメリットがあります:

  1. 依存関係の固定化: 具体的なクラスに直接依存するため、変更の影響が大きくなります。

  2. 拡張性の制限: 新しい決済方法を追加するたびに、processOrder 関数を変更する必要があります(開放/閉鎖原則に違反)。

  3. テスト困難性: 実装クラスを直接参照しているため、テスト時にモックに置き換えることが困難です。

  4. 複雑性の集中: 各決済プロセッサが複雑な初期化を必要とする場合、コードが冗長になります。

  5. インターフェース強制の欠如: すべての実装が同じインターフェースに従うことを保証する仕組みがありません。

実務的な視点では、コードの保守性、チーム開発での理解しやすさ、システム成長への対応力などが低下します。

実際の PHP プロジェクトでの活用場面

Factory Method パターンは、以下のような場面で特に有用です:

  1. データベース抽象化: 異なるデータベースエンジン向けのアダプターを提供します。

  2. サードパーティ API との連携: 異なる API プロバイダへの切り替えを容易にします。

  3. 拡張可能なフレームワーク設計: Laravel、Symfony などのフレームワークで多用されています。

まとめ

Factory Method パターンは一見すると冗長に見えるかもしれませんが、大規模なプロジェクトや長期的なメンテナンスが必要なコードベースでは、そのメリットが明確になります。

コードの分離性、テスト容易性、拡張性の向上など、多くの利点があります。適切な場面で使用することで、より堅牢で保守しやすいアプリケーションを構築することができます。

補足

この記事の内容について、身近なエンジニアから寄せられた疑問を元に補足記事を別途作成しています。
https://pointsandlines.jp/server-side/php/factory-method-pattern-qa-and-advanced

Discussion