Template Methodパターン
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 94/107。
Template Method パターンは、基本型でアルゴリズムの骨格を定義し、全体の構造を変更せずにサブタイプが特定のステップをオーバーライドできるようにします。Decorator がオブジェクトをラップして振る舞いを追加するのに対し、Template Method はアルゴリズムの流れを制御し、個々のステップをカスタマイズできるようにします。
Goでは継承がないため、構造体の埋め込みとインターフェースを組み合わせてこのパターンを実装します。「template」構造体はアルゴリズムの構造を定義し、カスタマイズ可能なメソッドを呼び出します:
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は、データのインポート/エクスポートパイプライン、レポートgenerator、テストフレームワークなど、同じ構造を共有しながら特定の手順が異なるalgorithmがある場合に最適です。
チャレンジ
簡単Template Method パターンを使って、レポート生成システムを構築しましょう!異なるレポートタイプが、data の収集、フォーマット、結果の出力という同じ生成プロセスに従いながら、それぞれ異なる方法で各ステップをカスタマイズするフレームワークを作成します。
コードを3つのファイルに分けて整理します。
report.go:interface と、レポート生成 algorithm を調整する base processor を Define します。レポート生成のステップを表す3つの methods を持つ
ReportGeneratorinterface を Create します。GatherData() string:レポートの raw data を取得しますFormatData(data string) string:data をレポートのフォーマットに変換しますOutputReport(formatted string) string:final の出力メッセージを生成します
ReportGeneratorの Implement を holds するReportProcessorstruct を構築します。3つのステップを gather、format、then output の順に executes し、final の結果を返すGenerate() stringmethod を追加します。generators.go:各ステップをカスタマイズする具体的なレポート generator を Implement します。2つのレポートタイプを Create します。
SalesReport:Regionfield(string)を持ちますGatherData()はsales-data-[region]を返しますFormatData(data)はSALES REPORT: [data]を返しますOutputReport(formatted)はPrinted: [formatted]を返します
InventoryReport:Warehousefield(string)を持ちますGatherData()はinventory-[warehouse]を返しますFormatData(data)は*** [data] ***を返しますOutputReport(formatted)はExported: [formatted]を返します
main.go:同じ algorithm の構造が、Implement に基づいて異なる結果を生成することを示します。レポートタイプ(
salesまたはinventory)と configuration の値(sales の場合は region、inventory の場合は warehouse)を読み取ります。appropriate な generator を Create し、それをReportProcessorでラップして、Generate()を Call し、結果を print します。
次の入力が提供されます。
- 1行目:レポートタイプ(
salesまたはinventory) - 2行目:configuration の値(region 名または warehouse 名)
たとえば、次の入力が与えられた場合:
sales
North出力は次のようになります。
Printed: SALES REPORT: sales-data-Northまた、次の入力が与えられた場合:
inventory
MainHub出力は次のようになります。
Exported: *** inventory-MainHub ***さらに、次の入力が与えられた場合:
sales
West出力は次のようになります。
Printed: SALES REPORT: sales-data-WestReportProcessor は常に同じ3つのステップを同じ順序で Call しますが、各レポートタイプはそれらのステップについて独自の Implement を提供することに注目してください。algorithm の骨格は固定されたまま、詳細は変化します。これが Template 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自分で練習してみよう: Goオンラインコンパイラ