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 ストラテジー
| 観点 | テンプレートメソッド | ストラテジー |
|---|---|---|
| 拡張方法 | 継承 (サブクラス) | 委譲 (コンポジション) |
| 結合度 | 高い | 低い |
| 柔軟性 | 中 | 高い |
| 用途 | アルゴリズムの骨格を固定 | アルゴリズムを差し替え |
実務での活用方法は関連書籍にも詳しい。