BehavioralC++verifiedVerified
Template Method Pattern in C++
Defines the skeleton of an algorithm in a base class, deferring certain steps to subclasses without changing the algorithm's overall structure.
How to Implement the Template Method Pattern in C++
1Step 1: Define the base class with the template method
class DataProcessor {
public:
virtual ~DataProcessor() = default;
// Template method — defines the algorithm skeleton
void process() {
auto data = readData();
auto parsed = parseData(data);
auto result = transform(parsed);
save(result);
}
protected:
// Steps to be overridden by subclasses
virtual std::string readData() = 0;
virtual std::vector<std::string> parseData(const std::string& raw) = 0;
// Optional hook with default behavior
virtual std::string transform(const std::vector<std::string>& data) {
std::string result;
for (const auto& item : data) result += item + "\n";
return result;
}
virtual void save(const std::string& result) {
std::cout << "Saved: " << result;
}
};2Step 2: Concrete implementation
class CsvProcessor : public DataProcessor {
protected:
std::string readData() override {
return "name,age\nAlice,30\nBob,25";
}
std::vector<std::string> parseData(const std::string& raw) override {
std::vector<std::string> lines;
size_t pos = 0, prev = 0;
while ((pos = raw.find('\n', prev)) != std::string::npos) {
lines.push_back(raw.substr(prev, pos - prev));
prev = pos + 1;
}
lines.push_back(raw.substr(prev));
return lines;
}
};
int main() {
CsvProcessor processor;
processor.process();
}#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <format>
#include <chrono>
#include <functional>
#include <stdexcept>
// [step] Define the template method base with hooks and timing
class ETLPipeline {
public:
virtual ~ETLPipeline() = default;
struct PipelineResult {
size_t recordsRead;
size_t recordsTransformed;
size_t recordsLoaded;
std::chrono::milliseconds duration;
};
// Template method
PipelineResult run() {
auto start = std::chrono::steady_clock::now();
beforeExtract();
auto raw = extract();
afterExtract(raw.size());
beforeTransform();
auto transformed = transform(raw);
afterTransform(transformed.size());
beforeLoad();
load(transformed);
auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
return {raw.size(), transformed.size(), transformed.size(), dur};
}
protected:
// Required steps
virtual std::vector<std::string> extract() = 0;
virtual std::vector<std::string> transform(const std::vector<std::string>& data) = 0;
virtual void load(const std::vector<std::string>& data) = 0;
// Optional hooks
virtual void beforeExtract() {}
virtual void afterExtract(size_t count) {
std::cout << std::format("Extracted {} records\n", count);
}
virtual void beforeTransform() {}
virtual void afterTransform(size_t count) {
std::cout << std::format("Transformed {} records\n", count);
}
virtual void beforeLoad() {}
};
// [step] Concrete pipeline: API data to console
class ApiToConsolePipeline : public ETLPipeline {
std::string source_;
public:
explicit ApiToConsolePipeline(std::string source) : source_(std::move(source)) {}
protected:
std::vector<std::string> extract() override {
// Simulate API fetch
return {"Alice:30", "Bob:25", "Charlie:35"};
}
std::vector<std::string> transform(const std::vector<std::string>& data) override {
std::vector<std::string> result;
for (const auto& record : data) {
auto pos = record.find(':');
if (pos != std::string::npos) {
auto name = record.substr(0, pos);
auto age = record.substr(pos + 1);
result.push_back(std::format("{{\"name\":\"{}\",\"age\":{}}}", name, age));
}
}
return result;
}
void load(const std::vector<std::string>& data) override {
for (const auto& record : data)
std::cout << std::format("[LOAD] {}\n", record);
}
void beforeExtract() override {
std::cout << std::format("Fetching from {}...\n", source_);
}
};
// [step] Another concrete pipeline: with filtering
class FilteredPipeline : public ApiToConsolePipeline {
std::function<bool(const std::string&)> filter_;
public:
FilteredPipeline(std::string source, std::function<bool(const std::string&)> filter)
: ApiToConsolePipeline(std::move(source)), filter_(std::move(filter)) {}
protected:
std::vector<std::string> transform(const std::vector<std::string>& data) override {
auto all = ApiToConsolePipeline::transform(data);
std::vector<std::string> filtered;
for (auto& item : all)
if (filter_(item)) filtered.push_back(std::move(item));
return filtered;
}
};
int main() {
ApiToConsolePipeline pipeline("https://api.example.com/users");
auto result = pipeline.run();
std::cout << std::format("\nPipeline: {} read, {} transformed in {}ms\n",
result.recordsRead, result.recordsTransformed, result.duration.count());
std::cout << "\n--- Filtered pipeline ---\n";
FilteredPipeline filtered("https://api.example.com/users",
[](const std::string& s) { return s.find("Alice") != std::string::npos; });
auto r2 = filtered.run();
std::cout << std::format("Filtered result: {} records\n", r2.recordsLoaded);
}Template Method Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Template Method Pattern in the Real World
“Consider a recipe for baking bread. The overall process—mix, knead, let rise, bake, cool—is fixed. But the specific flour blend, kneading technique, and baking temperature are decisions left to the baker. The cookbook provides the invariant sequence; individual bakers customize the steps that can vary without disrupting the overall process.”