テンプレートメソッドパターン
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を使用します。
チャレンジ
簡単Template Method パターンを使用して、レポート生成(Report Generator)システムを構築しましょう。売上レポートや在庫レポートなど、異なる種類のレポートが同じ生成プロセスに従いつつ、各レポートタイプが特定のステップをカスタマイズできるフレームワークを作成します。これは、全体のワークフローは固定されている一方で、個々のステップが変化するという、Template Method パターンに最適なシナリオです。
コードは以下の3つのファイルに分けて構成します:
ReportGenerator.h: テンプレートメソッドを持つ抽象基底クラスを定義します。レポート生成アルゴリズムのスケルトンを定義する
ReportGeneratorクラスを作成します。テンプレートメソッドgenerateReport()は、以下のステップを順番に実行する必要があります:gatherData()— 純粋仮想関数。サブクラスで実装する必要があります。formatReport()— 純粋仮想関数。サブクラスで実装する必要があります。addHeader()—--- Report ---と出力するデフォルトの振る舞いを持つフックメソッド。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つの入力を読み取ります:
- 売上金額(整数)
- 在庫数(整数)
売上金額を使用して
SalesReportを作成し、生成します。次に、在庫数を使用してInventoryReportを作成し、生成します。読みやすさのために、2つのレポートの間に空行を出力してください。
例えば、入力が 15000 と 250 の場合:
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入力が 8500 と 120 の場合:
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;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス