🎃

デザインパターンを学ぶ #18 テンプレートメソッド(Template Method)

に公開

1. はじめに

今回は テンプレートメソッド(Template Method)パターン
目的は、処理の流れを抽象クラスで固定しつつ、一部の手順だけをサブクラスに任せることです。

フレームワークのライフサイクルやテストコードの setUp/tearDown などでもよく使われます。

2. テンプレートメソッドとは?

アルゴリズムの骨格(流れ)を抽象クラスに定めておき、具体的な実装部分をサブクラスに委ねる仕組みです。

登場役は以下のとおりです:

  • AbstractClass … テンプレートメソッドを定義(処理の流れを固定)
  • ConcreteClass … 差し替えポイントを具体的に実装

これにより「流れは必ず守られるが、中身は差し替えられる」設計が可能になります。

3. 実装イメージ(PHP)

<?php
abstract class DataImporter {
    // テンプレートメソッド(流れを固定)
    public final function run(string $path): void {
        $rows = $this->load($path);
        $this->validate($rows);
        $this->save($rows);
        $this->after();
    }

    protected function load(string $path): array {
        return file($path, FILE_IGNORE_NEW_LINES);
    }

    protected function save(array $rows): void {
        echo "Saved " . count($rows) . " rows\n";
    }

    // 差し替えポイント
    abstract protected function validate(array $rows): void;

    // フックメソッド(任意)
    protected function after(): void {}
}

class UserImporter extends DataImporter {
    protected function validate(array $rows): void {
        foreach ($rows as $i => $row) {
            if (!str_contains($row, '@')) {
                throw new InvalidArgumentException("Row {$i} invalid email");
            }
        }
    }
}

class ProductImporter extends DataImporter {
    protected function validate(array $rows): void {
        foreach ($rows as $i => $row) {
            if (!preg_match('/^SKU-/', $row)) {
                throw new InvalidArgumentException("Row {$i} invalid sku");
            }
        }
    }
}

// 実行例
(new UserImporter())->run('users.txt');
(new ProductImporter())->run('products.txt');

4. メリット・デメリット

メリット

  • アルゴリズムの流れを強制できる
  • 重複する処理を抽象クラスにまとめられる
  • 差し替えポイントを明確化できる

デメリット

  • 継承ベースなので柔軟性は委譲(Strategy等)に劣る
  • 抽象クラスが肥大化しやすい
  • ランタイムに手順を変えるのは難しい

5. 使いどころ

  • バッチ処理やインポート処理の共通フローを統一したいとき
  • フレームワークのライフサイクルフック(before/after)
  • テストの共通処理(setUp/tearDown)

流れは固定、中身は差し替え」が欲しいときに使えるのがテンプレートメソッドです。

Discussion