CreationalC++verifiedVerified
Builder Pattern in C++
Separates the construction of a complex object from its representation, allowing the same construction process to produce different results.
How to Implement the Builder Pattern in C++
1Step 1: Define the product
struct HttpRequest {
std::string method;
std::string url;
std::vector<std::pair<std::string, std::string>> headers;
std::optional<std::string> body;
};2Step 2: Implement the builder with fluent interface
class RequestBuilder {
HttpRequest req_;
public:
RequestBuilder& method(const std::string& m) { req_.method = m; return *this; }
RequestBuilder& url(const std::string& u) { req_.url = u; return *this; }
RequestBuilder& header(const std::string& key, const std::string& val) {
req_.headers.emplace_back(key, val);
return *this;
}
RequestBuilder& body(const std::string& b) { req_.body = b; return *this; }
HttpRequest build() { return std::move(req_); }
};3Step 3: Client code uses the builder
int main() {
auto request = RequestBuilder()
.method("POST")
.url("https://api.example.com/data")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token123")
.body(R"({"key":"value"})")
.build();
std::cout << request.method << " " << request.url << "\n";
for (const auto& [k, v] : request.headers)
std::cout << k << ": " << v << "\n";
if (request.body) std::cout << "Body: " << *request.body << "\n";
}#include <iostream>
#include <string>
#include <vector>
#include <optional>
#include <map>
#include <stdexcept>
#include <format>
#include <chrono>
#include <concepts>
// [step] Define the complex product with validation
struct QueryConfig {
std::string table;
std::vector<std::string> columns;
std::vector<std::string> whereClauses;
std::vector<std::pair<std::string, bool>> orderBy; // column, ascending
std::optional<int> limit;
std::optional<int> offset;
std::vector<std::string> joins;
std::optional<std::string> groupBy;
std::optional<std::string> having;
};
// [step] Builder with compile-time type state using templates
template <bool HasTable>
class QueryBuilder {
QueryConfig config_;
public:
QueryBuilder() = default;
explicit QueryBuilder(QueryConfig cfg) : config_(std::move(cfg)) {}
// Only available when no table is set
auto from(const std::string& table) -> QueryBuilder<true>
requires (!HasTable)
{
config_.table = table;
return QueryBuilder<true>(std::move(config_));
}
auto& select(const std::string& col) {
config_.columns.push_back(col);
return *this;
}
auto& selectAll() {
config_.columns = {"*"};
return *this;
}
auto& where(const std::string& clause) {
config_.whereClauses.push_back(clause);
return *this;
}
auto& orderBy(const std::string& col, bool ascending = true) {
config_.orderBy.emplace_back(col, ascending);
return *this;
}
auto& limit(int n) {
if (n <= 0) throw std::invalid_argument("Limit must be positive");
config_.limit = n;
return *this;
}
auto& offset(int n) {
config_.offset = n;
return *this;
}
auto& join(const std::string& joinClause) {
config_.joins.push_back(joinClause);
return *this;
}
auto& groupBy(const std::string& col) {
config_.groupBy = col;
return *this;
}
auto& having(const std::string& clause) {
config_.having = clause;
return *this;
}
// build() only compiles when table is set
std::string build() const requires HasTable {
std::string sql = "SELECT ";
if (config_.columns.empty()) sql += "*";
else {
for (size_t i = 0; i < config_.columns.size(); ++i) {
if (i > 0) sql += ", ";
sql += config_.columns[i];
}
}
sql += " FROM " + config_.table;
for (const auto& j : config_.joins)
sql += " " + j;
if (!config_.whereClauses.empty()) {
sql += " WHERE ";
for (size_t i = 0; i < config_.whereClauses.size(); ++i) {
if (i > 0) sql += " AND ";
sql += config_.whereClauses[i];
}
}
if (config_.groupBy) sql += " GROUP BY " + *config_.groupBy;
if (config_.having) sql += " HAVING " + *config_.having;
if (!config_.orderBy.empty()) {
sql += " ORDER BY ";
for (size_t i = 0; i < config_.orderBy.size(); ++i) {
if (i > 0) sql += ", ";
sql += config_.orderBy[i].first;
sql += config_.orderBy[i].second ? " ASC" : " DESC";
}
}
if (config_.limit) sql += std::format(" LIMIT {}", *config_.limit);
if (config_.offset) sql += std::format(" OFFSET {}", *config_.offset);
return sql;
}
};
// [step] Demonstrate the type-safe builder
int main() {
auto sql = QueryBuilder<false>()
.from("users")
.select("name")
.select("email")
.join("JOIN orders ON users.id = orders.user_id")
.where("users.active = 1")
.where("orders.total > 100")
.groupBy("users.id")
.having("COUNT(orders.id) > 3")
.orderBy("name")
.limit(10)
.offset(20)
.build();
std::cout << sql << "\n";
// This would NOT compile (no table set):
// QueryBuilder<false>().select("x").build();
}Builder Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Builder Pattern in the Real World
“Consider ordering a custom sandwich at a deli. You tell the sandwich artist (builder) each step — bread type, protein, toppings, sauce — and they assemble it in the right order. You don’t need to know how to layer ingredients properly; you just specify what you want, and the builder hands you a finished sandwich.”