#03 デザインパターン入門
Factory Method——オブジェクト生成を子クラスに任せる
問題: 種類が増えるたびにコードを修正しなければならない
通知機能を実装するとき、最初は「メールだけ」でよかったのに、あとから「SMSも送りたい」「プッシュ通知も追加したい」と要件が増えることがあります。
// 問題のあるコード
class NotificationService
{
public function send(string $type, string $message): void
{
if ($type === 'email') {
$notifier = new EmailNotifier();
} elseif ($type === 'sms') {
$notifier = new SmsNotifier();
} elseif ($type === 'push') {
$notifier = new PushNotifier(); // 追加するたびにここを修正
}
$notifier->send($message);
}
}
種類が増えるたびに if/elseif を追加する必要があり、Open/Closed Principle(拡張に開き、修正に閉じる)に違反します。
パターン: Factory Method
Factory Methodパターンは、オブジェクトの生成を専用のメソッド(ファクトリメソッド)に委ね、そのメソッドを子クラスがオーバーライドして具体的な型を決めるパターンです。
+-----------------------------+
| Notifier <<interface>> |
+-----------------------------+
| + send(message: string) |
+-----------------------------+
△
|
+--------+--------+----------+
| | |
EmailNotifier SmsNotifier PushNotifier
+-----------------------------+
| NotifierFactory (abstract)|
+-----------------------------+
| + createNotifier(): Notifier| ← ファクトリメソッド(抽象)
| + notify(message: string) |
+-----------------------------+
△
|
+--------+--------+-----------+
| | |
EmailFactory SmsFactory PushFactory
PHP 8.x での実装
まずNotifierのインターフェースと具体的な実装を定義します。
<?php
// 通知の共通インターフェース
interface Notifier
{
public function send(string $message): void;
}
// Email通知
class EmailNotifier implements Notifier
{
public function __construct(private readonly string $to) {}
public function send(string $message): void
{
echo "Sending email to {$this->to}: {$message}\n";
// 実際のメール送信処理
}
}
// SMS通知
class SmsNotifier implements Notifier
{
public function __construct(private readonly string $phoneNumber) {}
public function send(string $message): void
{
echo "Sending SMS to {$this->phoneNumber}: {$message}\n";
// 実際のSMS送信処理
}
}
// プッシュ通知
class PushNotifier implements Notifier
{
public function __construct(private readonly string $deviceToken) {}
public function send(string $message): void
{
echo "Sending push to {$this->deviceToken}: {$message}\n";
// 実際のプッシュ通知処理
}
}
次にファクトリの抽象クラスと具体的なファクトリを実装します。
<?php
// 抽象ファクトリクラス
abstract class NotifierFactory
{
// ファクトリメソッド: 子クラスがオーバーライドして具体型を返す
abstract protected function createNotifier(): Notifier;
// テンプレートとなるメソッド: 生成と利用をセットにする
public function notify(string $message): void
{
$notifier = $this->createNotifier();
$notifier->send($message);
}
}
// Email用ファクトリ
class EmailNotifierFactory extends NotifierFactory
{
public function __construct(private readonly string $email) {}
protected function createNotifier(): Notifier
{
return new EmailNotifier($this->email);
}
}
// SMS用ファクトリ
class SmsNotifierFactory extends NotifierFactory
{
public function __construct(private readonly string $phone) {}
protected function createNotifier(): Notifier
{
return new SmsNotifier($this->phone);
}
}
// Push用ファクトリ
class PushNotifierFactory extends NotifierFactory
{
public function __construct(private readonly string $token) {}
protected function createNotifier(): Notifier
{
return new PushNotifier($this->token);
}
}
使う側のコードはこうなります。
// 使用例
function sendWelcomeNotification(NotifierFactory $factory): void
{
$factory->notify("ようこそ!アカウントが作成されました。");
}
// 通知方法をファクトリで切り替えるだけ
sendWelcomeNotification(new EmailNotifierFactory('user@example.com'));
sendWelcomeNotification(new SmsNotifierFactory('090-1234-5678'));
sendWelcomeNotification(new PushNotifierFactory('device_token_abc'));
新しい通知方法(例: Slack)を追加するときは、SlackNotifier と SlackNotifierFactory を追加するだけで、既存のコードは一切変更不要です。
Abstract Factory との違い
よく混同される Abstract Factory との違いを整理しておきましょう。
| 比較項目 | Factory Method | Abstract Factory |
|---|---|---|
| 目的 | ひとつの製品の生成を委譲 | 関連する複数の製品群をまとめて生成 |
| 単位 | メソッド単位 | クラス単位 |
| 例 | Notifier を作る | UI部品一式(ボタン・フォーム・ダイアログ)をまとめて作る |
// Abstract Factoryのイメージ
interface UiFactory
{
public function createButton(): Button;
public function createInput(): Input;
public function createDialog(): Dialog;
}
class DarkThemeFactory implements UiFactory
{
public function createButton(): Button { return new DarkButton(); }
public function createInput(): Input { return new DarkInput(); }
public function createDialog(): Dialog { return new DarkDialog(); }
}
Laravelでのファクトリ活用例
Laravelのモデルファクトリ(database/factories)もFactory Methodパターンの考え方を採用しています。テスト用のデータ生成をファクトリクラスに集約します。
// database/factories/UserFactory.php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
protected $model = User::class;
// definition() がファクトリメソッドに相当
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'role' => 'user',
];
}
// 状態メソッドで派生型を定義
public function admin(): static
{
return $this->state(['role' => 'admin']);
}
}
// テストコード
$user = User::factory()->create(); // 通常ユーザー
$admin = User::factory()->admin()->create(); // 管理者ユーザー
また、通知チャンネルの選択にも同様の考え方が使えます。
// app/Notifications/UserRegistered.php
class UserRegistered extends Notification
{
public function via(object $notifiable): array
{
// ユーザーの設定に応じてチャンネルを切り替え
return match ($notifiable->notification_preference) {
'sms' => ['nexmo'],
'push' => ['fcm'],
default => ['mail'],
};
}
}
まとめ
- Factory Methodは「どのクラスを生成するか」を子クラスに委ねるパターン
if/elseifによる生成分岐をなくし、拡張に強い設計になる- Abstract Factoryは複数の関連オブジェクトをまとめて生成するパターン
- Laravelのモデルファクトリ・通知チャンネルでも同じ考え方が使われている
次回はBuilderパターンで、複雑なオブジェクトを段階的に組み立てる方法を解説します。