Factoryパターン
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 72/91。
Factory Pattern(ファクトリパターン)は、オブジェクトの生成を別のクラスやメソッドに委譲する生成に関するデザインパターンです。コードの至る所で直接 new を使用する代わりに、ファクトリにオブジェクトの生成を依頼します。
このパターンは、共通のインターフェースを共有する複数の関連クラスがあり、ある条件に基づいてどのクラスをインスタンス化するかを決定したい場合に特に便利です。さまざまなチャネルを介してメッセージを送信できる通知システムを考えてみましょう。
<?php
interface Notification {
public function send(string $message): string;
}
class EmailNotification implements Notification {
public function send(string $message): string {
return "Email: $message";
}
}
class SmsNotification implements Notification {
public function send(string $message): string {
return "SMS: $message";
}
}
ファクトリがないと、至る所に if 文や new の呼び出しが散在することになります。ファクトリは、このロジックを一箇所に集約します:
<?php
class NotificationFactory {
public static function create(string $type): Notification {
return match($type) {
'email' => new EmailNotification(),
'sms' => new SmsNotification(),
default => throw new InvalidArgumentException("Unknown type: $type")
};
}
}
$notification = NotificationFactory::create('email');
echo $notification->send('Hello!');
出力:
Email: Hello!主な利点は、コードが具体的なクラスではなく Notification インターフェースに依存することです。後で PushNotification クラスを追加する場合、ファクトリを更新するだけで済みます。ファクトリを使用しているすべてのコードは、変更を加えることなく自動的に新しい型にアクセスできるようになります。
チャレンジ
簡単Factoryパターンを使用して、ドキュメント生成システムを構築しましょう。異なるドキュメントタイプ(PDF、HTML、プレーンテキスト)は共通のインターフェースを共有しますが、異なる出力を生成します。これは、オブジェクトの生成をFactoryに集約するのに最適なシナリオです。
コードは4つのファイルに分けて構成します:
Document.php— すべてのドキュメントタイプが従うべき規約を定義するDocumentインターフェースを作成します。これには、コンテンツを受け取り、フォーマットされたドキュメント出力を返す単一のメソッドrender(string $content): stringを含める必要があります。Documents.php— Documentインターフェースを読み込み、それを実装する3つのクラスを作成します:PdfDocument— そのrender()メソッドは"PDF: [content]"を返します。HtmlDocument— そのrender()メソッドは"<html>[content]</html>"を返します。TextDocument— そのrender()メソッドは"TXT: [content]"を返します。
DocumentFactory.php— Documentsファイルを読み込み、静的メソッドcreate(string $type): Documentを持つDocumentFactoryクラスを作成します。このメソッドはmatchを使用して、タイプに基づいて適切なドキュメントオブジェクトを返す必要があります:"pdf"は新しいPdfDocumentを返します。"html"は新しいHtmlDocumentを返します。"text"は新しいTextDocumentを返します。- それ以外のタイプの場合は、
"Unknown document type: [type]"というメッセージとともにInvalidArgumentExceptionをスローする必要があります。
main.php— DocumentFactoryファイルを読み込みます。ドキュメントタイプとレンダリングするコンテンツの2つの入力を受け取ります。Factoryを使用して適切なドキュメントタイプを作成し、その
render()メソッドをコンテンツとともに呼び出して、結果を出力します。新しい行に、このパターンの価値を強調するために
"Factory benefit: Adding new types only requires updating the factory"と出力します。
Factoryパターンはコードの柔軟性を保ちます。後で MarkdownDocument を追加する必要がある場合、既存のすべてのコードは変更せずにそのまま動作し、Factoryを更新するだけで済みます。
チートシート
Factoryパターンは、コード全体で直接 new を使用する代わりに、オブジェクトの生成を別のクラスやメソッドに委譲します。
このパターンは、共通のインターフェースを共有する複数の関連クラスがあり、条件に基づいてどれをインスタンス化するかを決定する必要がある場合に便利です。
通知システムの例:
<?php
interface Notification {
public function send(string $message): string;
}
class EmailNotification implements Notification {
public function send(string $message): string {
return "Email: $message";
}
}
class SmsNotification implements Notification {
public function send(string $message): string {
return "SMS: $message";
}
}
Factoryはオブジェクト生成のロジックを一元化します:
<?php
class NotificationFactory {
public static function create(string $type): Notification {
return match($type) {
'email' => new EmailNotification(),
'sms' => new SmsNotification(),
default => throw new InvalidArgumentException("Unknown type: $type")
};
}
}
$notification = NotificationFactory::create('email');
echo $notification->send('Hello!'); // Output: Email: Hello!
主な利点:
- コードが具体的なクラスではなく、インターフェースに依存するようになる
- 新しいタイプを追加する際、Factoryを更新するだけで済む
- 既存のコードを修正することなく、新しいタイプに自動的にアクセスできるようになる
自分で試してみよう
<?php
require_once 'DocumentFactory.php';
// 入力を読み込む
$type = trim(fgets(STDIN));
$content = trim(fgets(STDIN));
// TODO: DocumentFactoryを使用して適切なドキュメントタイプを作成する
// TODO: コンテンツを指定してrender()メソッドを呼び出し、結果を出力する
// TODO: 新しい行に "Factory benefit: Adding new types only requires updating the factory" と出力する
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。