Menu
Coddy logo textTech

Template Method Pattern

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 97 of 110.

The Template Method pattern defines the skeleton of an algorithm in a base class, letting subclasses override specific steps without changing the overall structure. The base class controls the workflow, while subclasses customize individual steps.

This pattern uses inheritance: an abstract class defines a template method that calls a sequence of steps, some of which are abstract (must be implemented by subclasses) and some have default implementations:

abstract class DataProcessor {
  // Template method - defines the algorithm structure
  void process() {
    readData();
    processData();
    saveData();
  }

  void readData();      // Abstract - subclasses must implement
  void processData();   // Abstract - subclasses must implement

  void saveData() {     // Default implementation
    print('Data saved to database');
  }
}

class CsvProcessor extends DataProcessor {
  @override
  void readData() => print('Reading CSV file');

  @override
  void processData() => print('Parsing CSV rows');
}

class JsonProcessor extends DataProcessor {
  @override
  void readData() => print('Reading JSON file');

  @override
  void processData() => print('Parsing JSON objects');

  @override
  void saveData() => print('Saved to cloud storage');
}

void main() {
  var csv = CsvProcessor();
  csv.process();
  // Reading CSV file
  // Parsing CSV rows
  // Data saved to database

  var json = JsonProcessor();
  json.process();
  // Reading JSON file
  // Parsing JSON objects
  // Saved to cloud storage
}

The process() method is the template - it defines the order of operations. Subclasses implement readData() and processData() differently, and can optionally override saveData(). The key benefit is that the algorithm's structure stays consistent across all implementations while allowing flexibility in individual steps.

challenge icon

Challenge

Easy

Let's build a report generation system using the Template Method pattern! You'll create a framework where different types of reports follow the same overall structure, but each report type customizes specific steps like gathering data and formatting the output.

You'll organize your code into two files:

  • report_generator.dart: This file contains your abstract base class and concrete report implementations. Create an abstract class ReportGenerator with a template method called generate() that defines the report generation workflow in this order: fetchData(), analyzeData(), formatReport(), and deliver(). The fetchData() and formatReport() methods should be abstract (each report type handles these differently), while analyzeData() should print Analyzing data... and deliver() should print Report delivered via email. as default implementations. Then create two concrete report classes:
    • SalesReport - its fetchData() prints Fetching sales data from database... and its formatReport() prints Formatting as bar charts and tables.
    • InventoryReport - its fetchData() prints Fetching inventory levels from warehouse..., its formatReport() prints Formatting as inventory list., and it overrides deliver() to print Report sent to warehouse manager.
  • main.dart: Import your report generator file and demonstrate how the Template Method pattern keeps the algorithm structure consistent while allowing customization. Create a SalesReport and call its generate() method. Then print an empty line for separation. Create an InventoryReport and call its generate() method as well.

Notice how both reports follow the same four-step process, but each customizes the steps that matter for its specific purpose. The InventoryReport even overrides the default delivery method to send reports to a different recipient!

Expected output:

Fetching sales data from database...
Analyzing data...
Formatting as bar charts and tables.
Report delivered via email.

Fetching inventory levels from warehouse...
Analyzing data...
Formatting as inventory list.
Report sent to warehouse manager.

Cheat sheet

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

An abstract class defines a template method that calls a sequence of steps. Some steps are abstract (must be implemented by subclasses) and some have default implementations:

abstract class DataProcessor {
  // Template method - defines the algorithm structure
  void process() {
    readData();
    processData();
    saveData();
  }

  void readData();      // Abstract - subclasses must implement
  void processData();   // Abstract - subclasses must implement

  void saveData() {     // Default implementation
    print('Data saved to database');
  }
}

class CsvProcessor extends DataProcessor {
  @override
  void readData() => print('Reading CSV file');

  @override
  void processData() => print('Parsing CSV rows');
}

class JsonProcessor extends DataProcessor {
  @override
  void readData() => print('Reading JSON file');

  @override
  void processData() => print('Parsing JSON objects');

  @override
  void saveData() => print('Saved to cloud storage');
}

The template method controls the workflow order. Subclasses customize individual steps by implementing abstract methods and optionally overriding default implementations. The algorithm's structure stays consistent across all implementations.

Try it yourself

import 'report_generator.dart';

void main() {
  // TODO: Create a SalesReport and call its generate() method

  // TODO: Print an empty line for separation

  // TODO: Create an InventoryReport and call its generate() method
}
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