LESSON 30分

ストーリー

高橋アーキテクト
オブジェクトを生成するコードが至る所に散在していないか?
高橋アーキテクト
new キーワードを直接使う場所が多いと、生成ロジックの変更が大変になる。Factory パターンは、オブジェクトの生成を一箇所にまとめ、呼び出し側を具体クラスから解放するパターンだ

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 MethodAbstract Factory
生成対象1種類のオブジェクト関連するオブジェクト群
実現方法メソッドのオーバーライドオブジェクトの委譲
使い分け種類を切り替えたいファミリーの一貫性を保ちたい
複雑さ低いやや高い

高橋アーキテクトのアドバイス:

「迷ったらまず Factory Method から始めよう。ファミリーで生成する必要が出てきたら Abstract Factory に進化させればいい。最初から複雑なパターンを適用する必要はない」


まとめ

ポイント内容
Factory Methodオブジェクト生成をサブクラスに委ねる
Abstract Factory関連するオブジェクト群をまとめて生成する
共通の効果呼び出し側を具体クラスから解放する
OCP との関連新しい種類の追加が容易になる

チェックリスト

  • Factory Method パターンの構造を説明できる
  • Abstract Factory パターンの構造を説明できる
  • 2つのパターンの使い分けを判断できる

次のステップへ

次は Builder パターンと Singleton パターンを学びます。複雑なオブジェクトの組み立てと、インスタンス数の制御を扱います。


推定読了時間: 30分