Template Pattern explained simply
Ever found yourself writing similar logic over and over, only to change a few steps each time? That’s exactly what the Template Pattern helps you solve.
The Template Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It lets you reuse algorithm structure while letting subclasses refine certain steps without changing the overall logic.
What is the Template Pattern?
At its core, the Template Pattern:
- Defines the outline (template) of an algorithm.
- Implements the invariant parts (that don’t change).
- Leaves the changing parts (abstract steps) to be defined by subclasses.
Think of it like a recipe: the steps are fixed, but the ingredients may vary.
Class Diagram
Here’s how the pattern looks structurally. The abstract class holds the template method and the abstract steps, while each concrete class fills in the blanks.
Real-Life Analogy
Imagine you’re making a cup of tea or coffee. The process is almost the same:
- Boil water
- Brew drink
- Pour into cup
- Add condiments
The steps are identical, but “brew drink” and “add condiments” differ. The recipe itself is the template – you just swap out the details.
That’s the same idea behind the Template Pattern in code. You lock down the sequence of steps and let subclasses decide what happens inside each one.
Template Pattern in Code (Java)
Let’s use a practical example: a data export pipeline. You have a fixed process – connect, extract, transform, write – but the output format changes depending on whether you want CSV or JSON.
First, the abstract class with the template method:
public abstract class DataExporter {
// Template method — defines the algorithm skeleton
public final void export() {
connectToSource();
String rawData = extractData();
String transformed = transformData(rawData);
writeOutput(transformed);
System.out.println("Export complete.");
}
// Abstract steps — subclasses fill these in
protected abstract void connectToSource();
protected abstract String extractData();
protected abstract String transformData(String data);
protected abstract void writeOutput(String data);
}
The export() method is final so no subclass can mess with the order of operations. That’s the whole point of the pattern – the structure stays locked.
Now the concrete classes. First, a CSV exporter:
public class CSVExporter extends DataExporter {
@Override
protected void connectToSource() {
System.out.println("Connecting to relational database...");
}
@Override
protected String extractData() {
System.out.println("Running SQL query...");
return "id,name,email\n1,Alice,[email protected]\n2,Bob,[email protected]";
}
@Override
protected String transformData(String data) {
System.out.println("Data already in CSV format, skipping transformation.");
return data;
}
@Override
protected void writeOutput(String data) {
System.out.println("Writing to output.csv:\n" + data);
}
}
And a JSON exporter:
public class JSONExporter extends DataExporter {
@Override
protected void connectToSource() {
System.out.println("Connecting to relational database...");
}
@Override
protected String extractData() {
System.out.println("Running SQL query...");
return "id,name,email\n1,Alice,[email protected]\n2,Bob,[email protected]";
}
@Override
protected String transformData(String data) {
System.out.println("Converting rows to JSON array...");
// In a real app you'd parse and serialize properly
return "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";
}
@Override
protected void writeOutput(String data) {
System.out.println("Writing to output.json:\n" + data);
}
}
And here’s how you use them:
public class MainProgram {
public static void main(String[] args) {
DataExporter csvExport = new CSVExporter();
csvExport.export();
System.out.println();
DataExporter jsonExport = new JSONExporter();
jsonExport.export();
}
}
Output:
Connecting to relational database...
Running SQL query...
Data already in CSV format, skipping transformation.
Writing to output.csv:
id,name,email
1,Alice,[email protected]
2,Bob,[email protected]
Export complete.
Connecting to relational database...
Running SQL query...
Converting rows to JSON array...
Writing to output.json:
[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]
Export complete.
Notice how both exporters follow the exact same four-step sequence. The only thing that changes is what happens inside each step. That’s the template pattern doing its job.
Key Components
- Abstract Class (
DataExporter): Holds the template method and declares abstract steps that subclasses must implement. - Template Method (
export()): Defines the fixed algorithm structure. Markedfinalso subclasses can’t override the sequence. - Concrete Classes (
CSVExporter,JSONExporter): Provide the actual logic for each step – connecting, extracting, transforming, and writing data in their own way.
When to Use the Template Pattern?
- When multiple classes share the same algorithm structure but differ in individual steps.
- When you want to avoid code duplication across similar workflows.
- When you need tight control over the algorithm’s structure while still allowing customization of specific parts.
Advantages
- Promotes code reuse. You write the algorithm once and never repeat it.
- Ensures consistent algorithm structure across all implementations.
- Follows the Hollywood Principle: “Don’t call us, we’ll call you.” The base class controls the flow, not the subclasses.
Disadvantages
- Requires inheritance, which can reduce flexibility compared to composition-based approaches.
- Can lead to class explosion if you end up with dozens of subclasses for minor variations.
- Harder to follow for beginners because the control flow bounces between parent and child classes.
Real-World Use Cases
- ETL pipelines and report generators: exactly the kind of connect-extract-transform-load workflow we showed above. The pipeline structure stays the same, but each data source or output format gets its own implementation.
- Frameworks with execution flows: Spring’s
AbstractController, Servlet’sHttpServlet(whereservice()is the template anddoGet()/doPost()are the steps you override). - Validation and processing pipelines: parse input, validate, process, return result – same skeleton, different rules.
- Build systems and CI/CD: checkout, compile, test, deploy – fixed order, customizable steps.
Final Thoughts
The Template Pattern is perfect when you have an algorithm with a fixed structure but with parts that vary. It helps you write clean, reusable, and maintainable code.
Next time you find repeated logic that only changes in a few places – reach for the Template Pattern.