Menu
Coddy logo textTech

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

Template Methodは、データのインポート/エクスポートパイプライン、レポートgenerator、テストフレームワークなど、同じ構造を共有しながら特定の手順が異なるalgorithmがある場合に最適です。

challenge icon

チャレンジ

簡単

Template Method パターンを使って、レポート生成システムを構築しましょう!異なるレポートタイプが、data の収集、フォーマット、結果の出力という同じ生成プロセスに従いながら、それぞれ異なる方法で各ステップをカスタマイズするフレームワークを作成します。

コードを3つのファイルに分けて整理します。

  • report.go:interface と、レポート生成 algorithm を調整する base processor を Define します。

    レポート生成のステップを表す3つの methods を持つ ReportGenerator interface を Create します。

    • GatherData() string:レポートの raw data を取得します
    • FormatData(data string) string:data をレポートのフォーマットに変換します
    • OutputReport(formatted string) string:final の出力メッセージを生成します

    ReportGenerator の Implement を holds する ReportProcessor struct を構築します。3つのステップを gather、format、then output の順に executes し、final の結果を返す Generate() string method を追加します。

  • generators.go:各ステップをカスタマイズする具体的なレポート generator を Implement します。

    2つのレポートタイプを Create します。

    • SalesReportRegion field(string)を持ちます
      • GatherData()sales-data-[region] を返します
      • FormatData(data)SALES REPORT: [data] を返します
      • OutputReport(formatted)Printed: [formatted] を返します
    • InventoryReportWarehouse field(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-West

ReportProcessor は常に同じ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")
}
quiz icon腕試し

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

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

自分で練習してみよう: Goオンラインコンパイラ