テンプレートメソッドパターン
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 73/87。
Template Method パターンは、アルゴリズムの骨組みを基底クラスで定義し、全体の構造を変えることなく、特定のステップをサブクラスでオーバーライドできるようにする振る舞いに関するデザインパターンです。料理のレシピのように考えてみてください。一般的な調理工程は同じですが、個々の材料やテクニックは異なる場合があります。
このパターンは、一連のステップを呼び出すテンプレートメソッドを持つ抽象クラスを使用します。一部のステップにはデフォルトの実装がありますが、他のステップは抽象的であり、サブクラスによって実装される必要があります:
abstract class DataProcessor {
// テンプレートメソッド - アルゴリズムの構造を定義します
public final void process() {
readData();
processData();
saveData();
}
abstract void readData();
abstract void processData();
// デフォルトの実装(フックメソッド)
void saveData() {
System.out.println("Saving to default location");
}
}
class CSVProcessor extends DataProcessor {
void readData() {
System.out.println("Reading CSV file");
}
void processData() {
System.out.println("Parsing CSV data");
}
}
class XMLProcessor extends DataProcessor {
void readData() {
System.out.println("Reading XML file");
}
void processData() {
System.out.println("Parsing XML data");
}
}テンプレートメソッドは、サブクラスがアルゴリズムの構造を変更するのを防ぐために、通常 final とマークされます。呼び出されると、すべてのサブクラスに対して同じシーケンスを実行します:
DataProcessor csv = new CSVProcessor();
csv.process();
// 出力:
// CSVファイルの読み込み
// CSVデータの解析
// デフォルトの場所に保存テンプレートメソッドパターンは、同じ構造を共有しながら特定のステップが異なるアルゴリズムがある場合に最適です。共通のロジックを基底クラスに配置し、継承を通じてカスタマイズを可能にすることで、コードの再利用を促進します。
チャレンジ
簡単Template Method パターンを使用して、ドキュメント生成システムを構築しましょう!異なるドキュメントタイプ(レポートと請求書)が同じ生成プロセスに従いつつ、特定のステップをカスタマイズできるフレームワークを作成します。これは、全体のワークフローは固定されているが、詳細はドキュメントタイプによって異なるというシナリオに最適です。
コードは4つのファイルに分けて構成します:
DocumentGenerator.java: テンプレートメソッドを定義する抽象基底クラスを作成します。DocumentGeneratorには以下を含める必要があります:createHeader()、createBody()、createFooter()の3つのステップを順番に呼び出すことでアルゴリズムのスケルトンを定義する、finalメソッドgenerateDocument()。サブクラスが実装しなければならない2つの抽象メソッド:
createHeader()とcreateBody()。--- End of Document ---と出力するデフォルト実装を持つフックメソッドcreateFooter()。ReportGenerator.java: レポートを生成するための具体的なサブクラスを作成します。コンストラクタでレポートのタイトル (String) を受け取るようにします。createHeader()メソッドは=== REPORT: [title] ===を出力します。createBody()メソッドはReport content goes here...を出力します。継承されたデフォルトのフッターを使用します。
InvoiceGenerator.java: 請求書を生成するための別の具体的なサブクラスを作成します。コンストラクタで顧客名 (String) と金額 (double) を受け取るようにします。createHeader()メソッドは*** INVOICE ***を出力します。createBody()メソッドは、1行目にCustomer: [name]、次の行にAmount Due: $[amount]を出力します(金額は小数点以下2桁にフォーマットしてください)。createFooter()をオーバーライドして、Thank you for your business!と出力します。Main.java: テンプレートメソッドシステムをまとめましょう!レポートのタイトル (String)、顧客名 (String)、請求書の金額 (double) の3つの入力を受け取ります。タイトルを指定して
ReportGeneratorを作成し、generateDocument()を呼び出します。区切りのために空行を出力します。次に、顧客名と金額を指定してInvoiceGeneratorを作成し、generateDocument()を呼び出します。
入力は、レポートのタイトル (String)、顧客名 (String)、請求書の金額 (double) の順に3つ与えられます。
例えば、入力が Q4 Sales、Acme Corp、1500.00 の場合、出力は以下のようになります:
=== REPORT: Q4 Sales ===
Report content goes here...
--- End of Document ---
*** INVOICE ***
Customer: Acme Corp
Amount Due: $1500.00
Thank you for your business!両方のドキュメントタイプが、テンプレートメソッドで定義された同じ3ステップのプロセスに従いながら、各ステップを異なる方法でカスタマイズしていることに注目してください。ReportGenerator はデフォルトのフッターを使用し、InvoiceGenerator は独自のフッターを提供しています。これが Template Method パターンにおけるフックメソッドの威力です!
チートシート
Template Method パターンは、アルゴリズムのスケルトン(骨組み)を基底クラスで定義し、全体の構造を変えることなく、特定のステップをサブクラスでオーバーライドできるようにする振る舞いに関するデザインパターンです。
このパターンでは、一連のステップを呼び出すテンプレートメソッドを持つ抽象クラスを使用します。一部のステップにはデフォルトの実装がありますが、他のステップは抽象メソッドであり、サブクラスで実装する必要があります。
abstract class DataProcessor {
// Template method - defines the algorithm structure
public final void process() {
readData();
processData();
saveData();
}
abstract void readData();
abstract void processData();
// Default implementation (hook method)
void saveData() {
System.out.println("Saving to default location");
}
}具体的なサブクラスで、これらの抽象メソッドを実装します。
class CSVProcessor extends DataProcessor {
void readData() {
System.out.println("Reading CSV file");
}
void processData() {
System.out.println("Parsing CSV data");
}
}テンプレートメソッドは、サブクラスがアルゴリズムの構造を変更できないように final として宣言されます。呼び出されると、すべてのサブクラスに対して同じシーケンスを実行します。
DataProcessor csv = new CSVProcessor();
csv.process();
// Output:
// Reading CSV file
// Parsing CSV data
// Saving to default location主な特徴:
- テンプレートメソッドは
finalであり、アルゴリズムの構造を定義します - 抽象メソッドはサブクラスで実装する必要があります
- フックメソッドは、必要に応じてオーバーライドできるデフォルトの実装を提供します
- 共通のロジックを基底クラスに配置することで、コードの再利用を促進します
自分で試してみよう
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 入力を読み込む
String reportTitle = scanner.nextLine();
String customerName = scanner.nextLine();
double invoiceAmount = scanner.nextDouble();
// TODO: タイトルを指定して ReportGenerator を作成し、 generateDocument() を呼び出す
// TODO: 区切りのために空行を出力する
// TODO: 顧客名と金額を指定して InvoiceGenerator を作成し、 generateDocument() を呼び出す
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造