Menu
Coddy logo textTech

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-data

Template Methodは、データインポート/エクスポートパイプライン、レポートジェネレーター、テストフレームワークのように、同じ構造を共有しながら特定のステップが異なるアルゴリズムを扱う場合に最適です。

challenge icon

チャレンジ

簡単

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)を持つ SalesReport
      • GatherData()"sales-data-[region]" を返します
      • FormatData(data)"SALES REPORT: [data]" を返します
      • OutputReport(formatted)"Printed: [formatted]" を返します
    • Warehouse フィールド(string)を持つ InventoryReport
      • GatherData()"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-West

ReportProcessor が常に同じ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-data

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")
}
quiz icon腕試し

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

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