Menu
Coddy logo textTech

Template Methodパターン

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

Template Method パターンは、base class で algorithm の骨格を定義し、subclass が algorithm 全体の構造を変更せずに特定のステップをオーバーライドできるようにします。algorithm 全体を入れ替える Strategy とは異なり、Template Method は algorithm を固定したまま、個々のステップをカスタマイズできるようにします。

base class はテンプレート method(algorithm)を実装し、サブクラスが提供する必要のある抽象または仮想 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 パターンの実際の動作です。アルゴリズムの構造は基底クラスで固定されますが、詳細は柔軟に変更できます。

自分で試してみよう

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

using namespace std;

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

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

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

自分で練習してみよう: C++オンラインコンパイラ