Template Method パターン

アルゴリズムの骨格を親クラスで定義し、具体的なステップをサブクラスに委ねるデザインパターン

設計パターンオブジェクト指向

Template Method パターンとは

Template Method パターンは、アルゴリズムの全体的な流れ (テンプレート) を親クラスで定義し、各ステップの具体的な実装をサブクラスに委ねる GoF デザインパターンである。「ハリウッドの原則 (Don't call us, we'll call you)」を体現する。

基本的な実装

abstract class DataExporter {
  // テンプレートメソッド: アルゴリズムの骨格
  async export(): Promise<void> {
    const data = await this.fetchData();
    const transformed = this.transform(data);
    await this.save(transformed);
    await this.notify();
  }

  // 抽象メソッド: サブクラスが実装
  protected abstract fetchData(): Promise<any[]>;
  protected abstract transform(data: any[]): string;
  protected abstract save(content: string): Promise<void>;

  // フックメソッド: デフォルト実装あり、オーバーライド可能
  protected async notify(): Promise<void> {
    console.log('Export completed');
  }
}

class CsvExporter extends DataExporter {
  protected async fetchData() { return db.query('SELECT * FROM orders'); }
  protected transform(data: any[]) { return data.map(r => Object.values(r).join(',')).join('\n'); }
  protected async save(content: string) { await fs.writeFile('export.csv', content); }
}

class S3JsonExporter extends DataExporter {
  protected async fetchData() { return ddb.scan({ TableName: 'Orders' }); }
  protected transform(data: any[]) { return JSON.stringify(data); }
  protected async save(content: string) { await s3.putObject({ Body: content }); }
  protected async notify() { await sns.publish({ Message: 'S3 export done' }); }
}

Strategy パターンとの違い

パターン 仕組み 変更の粒度
Template Method 継承でステップを差し替え アルゴリズムの一部
Strategy コンポジションでアルゴリズム全体を差し替え アルゴリズム全体

Template Method は「アルゴリズムの骨格は同じだが、一部のステップが異なる」場合に使う。Strategy は「アルゴリズム全体が異なる」場合に使う。

TypeScript では関数ベースが好まれる

TypeScript/JavaScript では、継承よりも関数の合成が好まれる。Template Method を高階関数で実現できる。

type ExportConfig<T> = {
  fetchData: () => Promise<T[]>;
  transform: (data: T[]) => string;
  save: (content: string) => Promise<void>;
  notify?: () => Promise<void>;
};

async function exportData<T>(config: ExportConfig<T>): Promise<void> {
  const data = await config.fetchData();
  const content = config.transform(data);
  await config.save(content);
  await config.notify?.();
}

// 使用
await exportData({
  fetchData: () => db.query('SELECT * FROM orders'),
  transform: (data) => JSON.stringify(data),
  save: (content) => s3.putObject({ Body: content }),
});

実務での活用

  • Express/Koa のミドルウェアチェーン
  • テストフレームワークの beforeEach / afterEach
  • React のライフサイクルメソッド (クラスコンポーネント)

テンプレートメソッド vs ストラテジー

観点 テンプレートメソッド ストラテジー
拡張方法 継承 (サブクラス) 委譲 (コンポジション)
結合度 高い 低い
柔軟性 高い
用途 アルゴリズムの骨格を固定 アルゴリズムを差し替え

実務での活用方法は関連書籍にも詳しい。

関連用語