ストーリー
Factory Method パターン
目的
オブジェクトの生成をサブクラスに委ねる。どの具体クラスを生成するかを、呼び出し側に知らせずに決定する。
パターンなしの問題
// 問題:呼び出し側が具体クラスを知っている
class NotificationService {
sendNotification(type: string, message: string): void {
let notification;
if (type === 'email') {
notification = new EmailNotification('smtp.example.com', 587);
} else if (type === 'sms') {
notification = new SmsNotification('api-key-123');
} else if (type === 'push') {
notification = new PushNotification('firebase-token');
}
notification.send(message);
}
}
Factory Method の実装
// 通知の共通インターフェース
interface Notification {
send(message: string): void;
}
// 具体クラス
class EmailNotification implements Notification {
send(message: string): void {
console.log(`Email: ${message}`);
}
}
class SmsNotification implements Notification {
send(message: string): void {
console.log(`SMS: ${message}`);
}
}
class PushNotification implements Notification {
send(message: string): void {
console.log(`Push: ${message}`);
}
}
// Creator(Factory Method を持つ抽象クラス)
abstract class NotificationCreator {
// Factory Method -- サブクラスが実装
abstract createNotification(): Notification;
// テンプレートメソッド
sendNotification(message: string): void {
const notification = this.createNotification();
notification.send(message);
}
}
// Concrete Creator
class EmailNotificationCreator extends NotificationCreator {
createNotification(): Notification {
return new EmailNotification();
}
}
class SmsNotificationCreator extends NotificationCreator {
createNotification(): Notification {
return new SmsNotification();
}
}
// 使い方
function notify(creator: NotificationCreator, message: string): void {
creator.sendNotification(message); // 具体クラスを知らない
}
notify(new EmailNotificationCreator(), 'Hello!');
notify(new SmsNotificationCreator(), 'Hello!');
TypeScript での簡易的な Factory
実務では、抽象クラスを使わずに関数やマップで実現することも多いです。
// Factory 関数
type NotificationType = 'email' | 'sms' | 'push';
const notificationFactories: Record<NotificationType, () => Notification> = {
email: () => new EmailNotification(),
sms: () => new SmsNotification(),
push: () => new PushNotification(),
};
function createNotification(type: NotificationType): Notification {
const factory = notificationFactories[type];
if (!factory) throw new Error(`Unknown type: ${type}`);
return factory();
}
Abstract Factory パターン
目的
関連するオブジェクト群をまとめて生成する。オブジェクトのファミリー全体の一貫性を保証する。
ユースケース:UIテーマ
// ボタンのインターフェース
interface Button {
render(): string;
}
// テキストフィールドのインターフェース
interface TextField {
render(): string;
}
// Abstract Factory
interface UIFactory {
createButton(): Button;
createTextField(): TextField;
}
// ライトテーマの具体実装
class LightButton implements Button {
render(): string { return '<button class="light-btn">Click</button>'; }
}
class LightTextField implements TextField {
render(): string { return '<input class="light-input" />'; }
}
class LightUIFactory implements UIFactory {
createButton(): Button { return new LightButton(); }
createTextField(): TextField { return new LightTextField(); }
}
// ダークテーマの具体実装
class DarkButton implements Button {
render(): string { return '<button class="dark-btn">Click</button>'; }
}
class DarkTextField implements TextField {
render(): string { return '<input class="dark-input" />'; }
}
class DarkUIFactory implements UIFactory {
createButton(): Button { return new DarkButton(); }
createTextField(): TextField { return new DarkTextField(); }
}
// 利用側:どのテーマかを知らずにUIを構築
class LoginForm {
private button: Button;
private usernameField: TextField;
private passwordField: TextField;
constructor(factory: UIFactory) {
this.button = factory.createButton();
this.usernameField = factory.createTextField();
this.passwordField = factory.createTextField();
}
render(): string {
return `
${this.usernameField.render()}
${this.passwordField.render()}
${this.button.render()}
`;
}
}
// テーマの切り替えは Factory を変えるだけ
const lightForm = new LoginForm(new LightUIFactory());
const darkForm = new LoginForm(new DarkUIFactory());
Factory Method vs Abstract Factory
| 観点 | Factory Method | Abstract Factory |
|---|---|---|
| 生成対象 | 1種類のオブジェクト | 関連するオブジェクト群 |
| 実現方法 | メソッドのオーバーライド | オブジェクトの委譲 |
| 使い分け | 種類を切り替えたい | ファミリーの一貫性を保ちたい |
| 複雑さ | 低い | やや高い |
高橋アーキテクトのアドバイス:
「迷ったらまず Factory Method から始めよう。ファミリーで生成する必要が出てきたら Abstract Factory に進化させればいい。最初から複雑なパターンを適用する必要はない」
まとめ
| ポイント | 内容 |
|---|---|
| Factory Method | オブジェクト生成をサブクラスに委ねる |
| Abstract Factory | 関連するオブジェクト群をまとめて生成する |
| 共通の効果 | 呼び出し側を具体クラスから解放する |
| OCP との関連 | 新しい種類の追加が容易になる |
チェックリスト
- Factory Method パターンの構造を説明できる
- Abstract Factory パターンの構造を説明できる
- 2つのパターンの使い分けを判断できる
次のステップへ
次は Builder パターンと Singleton パターンを学びます。複雑なオブジェクトの組み立てと、インスタンス数の制御を扱います。
推定読了時間: 30分