Menu
Coddy logo textTech

Template Method Pattern

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 94 of 107.

The Template Method pattern defines the skeleton of an algorithm in a base type, letting subtypes override specific steps without changing the overall structure. While Decorator adds behavior by wrapping objects, Template Method controls the algorithm's flow and allows customization of individual steps.

In Go, since there's no inheritance, we implement this pattern using struct embedding combined with interfaces. The "template" struct defines the algorithm structure and calls methods that can be customized:

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)
}

Concrete implementations provide their own versions of each step while the algorithm flow remains unchanged:

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 }

The template method Execute orchestrates the steps in a fixed order:

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 is ideal when you have algorithms that share the same structure but differ in specific steps, such as data import/export pipelines, report generators, or test frameworks.

challenge icon

Challenge

Easy

Let's build a report generation system using the Template Method pattern! You'll create a framework where different report types follow the same generation process—gathering data, formatting it, and outputting the result—but each report type customizes these steps differently.

You'll organize your code across three files:

  • report.go: Define your interface and the base processor that orchestrates the report generation algorithm.

    Create a ReportGenerator interface with three methods that represent the steps of report generation:

    • GatherData() string — retrieves the raw data for the report
    • FormatData(data string) string — transforms the data into the report's format
    • OutputReport(formatted string) string — produces the final output message

    Build a ReportProcessor struct that holds a ReportGenerator implementation. Add a Generate() string method that executes the three steps in order: gather, format, then output—returning the final result.

  • generators.go: Implement concrete report generators that customize each step.

    Create two report types:

    • SalesReport with a Region field (string)
      • GatherData() returns sales-data-[region]
      • FormatData(data) returns SALES REPORT: [data]
      • OutputReport(formatted) returns Printed: [formatted]
    • InventoryReport with a Warehouse field (string)
      • GatherData() returns inventory-[warehouse]
      • FormatData(data) returns *** [data] ***
      • OutputReport(formatted) returns Exported: [formatted]
  • main.go: Demonstrate how the same algorithm structure produces different results based on the implementation.

    Read the report type (sales or inventory) and the configuration value (region for sales, warehouse for inventory). Create the appropriate generator, wrap it in a ReportProcessor, call Generate(), and print the result.

The following inputs will be provided:

  • Line 1: Report type (sales or inventory)
  • Line 2: Configuration value (region name or warehouse name)

For example, given:

sales
North

Your output should be:

Printed: SALES REPORT: sales-data-North

And given:

inventory
MainHub

Your output should be:

Exported: *** inventory-MainHub ***

And given:

sales
West

Your output should be:

Printed: SALES REPORT: sales-data-West

Notice how the ReportProcessor always calls the same three steps in the same order, but each report type provides its own implementation of those steps. The algorithm's skeleton stays fixed while the details vary—that's the Template Method pattern in action!

Cheat sheet

The Template Method pattern defines the skeleton of an algorithm in a base type, letting subtypes override specific steps without changing the overall structure.

In Go, implement this pattern using struct embedding combined with interfaces. The "template" struct defines the algorithm structure and calls methods that can be customized:

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)
}

Concrete implementations provide their own versions of each step while the algorithm flow remains unchanged:

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 }

The template method orchestrates the steps in a fixed order:

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 is ideal when algorithms share the same structure but differ in specific steps, such as data import/export pipelines, report generators, or test frameworks.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Read report type
	scanner.Scan()
	reportType := scanner.Text()
	
	// Read configuration value (region or warehouse)
	scanner.Scan()
	configValue := scanner.Text()

	// TODO: Create the appropriate generator based on reportType
	// - If reportType is "sales", create a SalesReport with Region set to configValue
	// - If reportType is "inventory", create an InventoryReport with Warehouse set to configValue
	
	// TODO: Create a ReportProcessor with the generator
	
	// TODO: Call Generate() and print the result
	_ = reportType
	_ = configValue
	fmt.Println("TODO: Generate and print the report")
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming