Template Methodパターン
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 94/107。
Template Method パターンは、アルゴリズムのスケルトンを基本型で定義し、全体の構造を変えることなく、特定のステップをサブタイプでオーバーライドできるようにします。Decorator がオブジェクトをラップすることで振る舞いを追加するのに対し、Template Method はアルゴリズムのフローを制御し、個々のステップのカスタマイズを可能にします。
Goでは、継承が存在しないため、インターフェースと構造体の埋め込み(struct embedding)を組み合わせてこのパターンを実装します。「テンプレート」となる構造体がアルゴリズムの構造を定義し、カスタマイズ可能なメソッドを呼び出します。
type DataProcessor interface {
ReadData() string
ProcessData(data string) string
SaveData(result string) string
}
type BaseProcessor struct {
Impl DataProcessor
}
func (b *BaseProcessor) Execute() string {
data := b.Impl.ReadData()
result := b.Impl.ProcessData(data)
return b.Impl.SaveData(result)
}具体的な実装は、アルゴリズムのフローを維持したまま、各ステップに独自の処理を提供します:
type CSVProcessor struct{}
func (c CSVProcessor) ReadData() string { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string { return "Saved: " + r }
type JSONProcessor struct{}
func (j JSONProcessor) ReadData() string { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string { return "Stored: " + r }テンプレートメソッド Execute は、一連のステップを固定の順序で調整します:
csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute()) // 保存済み: processed-csv-data
jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // 格納済み: parsed-json-dataTemplate Methodは、データインポート/エクスポートパイプライン、レポートジェネレーター、テストフレームワークのように、同じ構造を共有しながら特定のステップが異なるアルゴリズムを扱う場合に最適です。
チャレンジ
簡単Template Method パターンを使用してレポート生成システムを構築しましょう!異なるレポートタイプが同じ生成プロセス(データの収集、フォーマット、結果の出力)に従いながら、各レポートタイプがこれらのステップを個別にカスタマイズするフレームワークを作成します。
コードは以下の3つのファイルに分けて構成します:
report.go: インターフェースと、レポート生成アルゴリズムをオーケストレートするベースプロセッサを定義します。レポート生成のステップを表す3つのメソッドを持つ
ReportGeneratorインターフェースを作成します:GatherData() string— レポートの生データを取得しますFormatData(data string) string— データをレポートの形式に変換しますOutputReport(formatted string) string— 最終的な出力メッセージを生成します
ReportGeneratorの実装を保持するReportProcessor構造体を構築します。収集、フォーマット、出力の3つのステップを順番に実行し、最終結果を返すGenerate() stringメソッドを追加します。generators.go: 各ステップをカスタマイズする具体的なレポートジェネレータを実装します。2つのレポートタイプを作成します:
Regionフィールド(string)を持つSalesReportGatherData()は"sales-data-[region]"を返しますFormatData(data)は"SALES REPORT: [data]"を返しますOutputReport(formatted)は"Printed: [formatted]"を返します
Warehouseフィールド(string)を持つInventoryReportGatherData()は"inventory-[warehouse]"を返しますFormatData(data)は"*** [data] ***"を返しますOutputReport(formatted)は"Exported: [formatted]"を返します
main.go: 同じアルゴリズム構造が、実装に基づいてどのように異なる結果を生み出すかを示します。レポートタイプ(
"sales"または"inventory")と設定値(売上の場合はリージョン、在庫の場合は倉庫)を読み取ります。適切なジェネレータを作成し、それをReportProcessorでラップしてGenerate()を呼び出し、結果をプリントします。
以下の入力が提供されます:
- 1行目: レポートタイプ(
"sales"または"inventory") - 2行目: 設定値(リージョン名または倉庫名)
例えば、以下が与えられた場合:
sales
North出力は以下のようになります:
Printed: SALES REPORT: sales-data-Northまた、以下が与えられた場合:
inventory
MainHub出力は以下のようになります:
Exported: *** inventory-MainHub ***また、以下が与えられた場合:
sales
West出力は以下のようになります:
Printed: SALES REPORT: sales-data-WestReportProcessor が常に同じ3つのステップを同じ順序で呼び出している一方で、各レポートタイプがそれらのステップの独自の実装を提供していることに注目してください。アルゴリズムのスケルトン(骨組み)は固定されたまま、詳細は変化します。これが Template Method パターンの真髄です!
チートシート
Template Method パターンは、アルゴリズムのスケルトンを基本型で定義し、全体の構造を変えることなく、サブタイプが特定のステップをオーバーライドできるようにします。
Go では、構造体の埋め込み(struct embedding)とインターフェースを組み合わせてこのパターンを実装します。「テンプレート」となる構造体がアルゴリズムの構造を定義し、カスタマイズ可能なメソッドを呼び出します。:
type DataProcessor interface {
ReadData() string
ProcessData(data string) string
SaveData(result string) string
}
type BaseProcessor struct {
Impl DataProcessor
}
func (b *BaseProcessor) Execute() string {
data := b.Impl.ReadData()
result := b.Impl.ProcessData(data)
return b.Impl.SaveData(result)
}具体的な実装は、アルゴリズムのフローを維持したまま、各ステップの独自のバージョンを提供します。:
type CSVProcessor struct{}
func (c CSVProcessor) ReadData() string { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string { return "Saved: " + r }
type JSONProcessor struct{}
func (j JSONProcessor) ReadData() string { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string { return "Stored: " + r }テンプレートメソッドは、固定された順序でステップを調整します。:
csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute()) // Saved: processed-csv-data
jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Stored: parsed-json-dataTemplate Method は、データ・インポート/エクスポート・パイプライン、レポート・ジェネレーター、テスト・フレームワークなど、アルゴリズムが同じ構造を共有しながら特定のステップが異なる場合に最適です。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// レポートの種類を読み込む
scanner.Scan()
reportType := scanner.Text()
// 設定値(地域または倉庫)を読み込む
scanner.Scan()
configValue := scanner.Text()
// TODO: reportTypeに基づいて適切なジェネレーターを作成する
// - reportTypeが "sales" の場合、RegionにconfigValueを設定したSalesReportを作成する
// - reportTypeが "inventory" の場合、WarehouseにconfigValueを設定したInventoryReportを作成する
// TODO: ジェネレーターを使用してReportProcessorを作成する
// TODO: Generate()を呼び出して結果を出力する
_ = reportType
_ = configValue
fmt.Println("TODO: Generate and print the report")
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー11標準ライブラリとOOP
io.Reader と io.Writersort.Interfacefmt.Stringer インターフェースencoding/json と構造体http.Handler インターフェースまとめ:REST API モデル14デザインパターン パート2
CommandパターンAdapterパターンDecoratorパターンTemplate MethodパターンStateパターンCompositeパターンDecoratorとしてのMiddleware