Menu
Coddy logo textTech

テンプレートメソッドパターン

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 99/104。

Template Method パターンは、アルゴリズムのスケルトン(骨組み)を基底クラスで定義し、アルゴリズム全体の構造を変えることなく、特定のステップをサブクラスでオーバーライドできるようにします。アルゴリズム全体を入れ替える Strategy パターンとは異なり、Template Method はアルゴリズムを固定したまま、個々のステップのカスタマイズを可能にします。

基底クラスはテンプレートメソッド(アルゴリズム)を実装し、サブクラスが提供しなければならない抽象メソッドまたは仮想メソッドを呼び出します:

#include <iostream>

class DataProcessor {
public:
    // テンプレートメソッド - アルゴリズムのスケルトンを定義します
    void process() {
        loadData();
        processData();
        saveResults();
    }
    
    virtual ~DataProcessor() = default;
    
protected:
    virtual void loadData() = 0;      // 実装が必須です
    virtual void processData() = 0;   // 実装が必須です
    
    // フック - デフォルトの動作を持つオプションのオーバーライド
    virtual void saveResults() {
        std::cout << "Saving to default location\n";
    }
};

class CSVProcessor : public DataProcessor {
protected:
    void loadData() override {
        std::cout << "Loading CSV file\n";
    }
    void processData() override {
        std::cout << "Parsing CSV data\n";
    }
};

class JSONProcessor : public DataProcessor {
protected:
    void loadData() override {
        std::cout << "Loading JSON file\n";
    }
    void processData() override {
        std::cout << "Parsing JSON data\n";
    }
    void saveResults() override {
        std::cout << "Saving to cloud storage\n";
    }
};

int main() {
    CSVProcessor csv;
    csv.process();  // デフォルトの saveResults を使用します
    
    JSONProcessor json;
    json.process(); // カスタムの saveResults を使用します
}

process() メソッドはテンプレートメソッドであり、固定された一連のステップを定義します。サブクラスは loadData()processData() を異なる方法で実装しますが、その順序が変わることはありません。saveResults() メソッドは「フック」であり、サブクラスが任意でオーバーライドできるデフォルトの実装を持っています。

不変のステップを持つアルゴリズムがあるが、特定の操作に柔軟性が必要な場合、または類似したクラス間でのコードの重複を避けたい場合に、Template Methodを使用します。

challenge icon

チャレンジ

簡単

Template Method パターンを使用して、レポート生成(Report Generator)システムを構築しましょう。売上レポートや在庫レポートなど、異なる種類のレポートが同じ生成プロセスに従いつつ、各レポートタイプが特定のステップをカスタマイズできるフレームワークを作成します。これは、全体のワークフローは固定されている一方で、個々のステップが変化するという、Template Method パターンに最適なシナリオです。

コードは以下の3つのファイルに分けて構成します:

  • ReportGenerator.h: テンプレートメソッドを持つ抽象基底クラスを定義します。

    レポート生成アルゴリズムのスケルトンを定義する ReportGenerator クラスを作成します。テンプレートメソッド generateReport() は、以下のステップを順番に実行する必要があります:

    1. gatherData() — 純粋仮想関数。サブクラスで実装する必要があります。
    2. formatReport() — 純粋仮想関数。サブクラスで実装する必要があります。
    3. addHeader()--- Report --- と出力するデフォルトの振る舞いを持つフックメソッド。
    4. printReport() — 純粋仮想関数。サブクラスで実装する必要があります。

    addHeader() メソッドは、サブクラスがヘッダーをカスタマイズするためにオプションでオーバーライドできるフックとして機能します。

  • Reports.h: 2つの具体的なレポート生成クラスを実装します。

    以下の SalesReport クラスを作成します:

    • gatherData()Gathering sales data from database と出力します。
    • formatReport()Formatting sales figures と出力します。
    • printReport()Sales Total: $[amount] と出力します(amount はコンストラクタに渡されます)。

    以下の InventoryReport クラスを作成します:

    • gatherData()Scanning inventory records と出力します。
    • formatReport()Organizing inventory by category と出力します。
    • addHeader() はフックをオーバーライドして === Inventory Report === と出力します。
    • printReport()Items in stock: [count] と出力します(count はコンストラクタに渡されます)。

    両方のクラスは、それぞれの値(amount または count)をコンストラクタ経由で受け取る必要があります。

  • main.cpp: Template Method パターンの動作を確認します。

    2つの入力を読み取ります:

    1. 売上金額(整数)
    2. 在庫数(整数)

    売上金額を使用して SalesReport を作成し、生成します。次に、在庫数を使用して InventoryReport を作成し、生成します。読みやすさのために、2つのレポートの間に空行を出力してください。

例えば、入力が 15000250 の場合:

Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $15000

Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 250

入力が 8500120 の場合:

Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $8500

Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 120

両方のレポートが generateReport() で定義されたのと全く同じステップの順序に従いながら、各レポートがそれらのステップを異なる方法で実装していることに注目してください。また、InventoryReport はフックメソッドをオーバーライドしてヘッダーをカスタマイズしていますが、SalesReport はデフォルトのものを使用しています。これが Template Method パターンの実例です。アルゴリズムの構造は基底クラスで固定されていますが、詳細は柔軟に変更可能です。

チートシート

Template Method パターンは、アルゴリズムのスケルトンを基底クラスで定義し、アルゴリズム全体の構造を変えることなく、サブクラスが特定のステップをオーバーライドできるようにします。

基底クラスはテンプレートメソッド(アルゴリズム)を実装し、サブクラスが提供しなければならない抽象メソッドまたは仮想メソッドを呼び出します:

#include <iostream>

class DataProcessor {
public:
    // テンプレートメソッド - アルゴリズムのスケルトンを定義します
    void process() {
        loadData();
        processData();
        saveResults();
    }
    
    virtual ~DataProcessor() = default;
    
    protected:
    virtual void loadData() = 0;      // 実装が必須です
    virtual void processData() = 0;   // 実装が必須です
    
    // フック - デフォルトの動作を持つオプションのオーバーライド
    virtual void saveResults() {
        std::cout << "Saving to default location\n";
    }
};

class CSVProcessor : public DataProcessor {
protected:
    void loadData() override {
        std::cout << "Loading CSV file\n";
    }
    void processData() override {
        std::cout << "Parsing CSV data\n";
    }
};

class JSONProcessor : public DataProcessor {
protected:
    void loadData() override {
        std::cout << "Loading JSON file\n";
    }
    void processData() override {
        std::cout << "Parsing JSON data\n";
    }
    void saveResults() override {
        std::cout << "Saving to cloud storage\n";
    }
};

テンプレートメソッドは、固定された一連のステップを定義します。サブクラスは抽象メソッドをそれぞれ異なる方法で実装しますが、順序が変わることはありません。フックメソッドにはデフォルトの実装があり、サブクラスは必要に応じてそれをオーバーライドできます。

不変のステップを持つアルゴリズムがあり、特定の操作に柔軟性が必要な場合、または類似したクラス間でのコードの重複を避けたい場合に、Template Method を使用します。

自分で試してみよう

#include <iostream>
#include "Reports.h"

using namespace std;

int main() {
    // 入力を読み込む
    int salesAmount;
    int inventoryCount;
    cin >> salesAmount;
    cin >> inventoryCount;
    
    // TODO: salesAmount を使用して SalesReport を作成し、生成する
    
    // TODO: レポートの間に空行を出力する
    
    // TODO: inventoryCount を使用して InventoryReport を作成し、生成する
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン