Builder Pattern
Builder Pattern: Constructing Complex Objects Step by Step
Section titled “Builder Pattern: Constructing Complex Objects Step by Step”Now let’s dive into the Builder Pattern - a creational design pattern that helps you construct complex objects step by step.
Why Builder Pattern?
Section titled “Why Builder Pattern?”Imagine ordering a custom pizza. You don’t just say “I want a pizza” - you specify the size, crust type, sauce, cheese, and toppings one by one. The Builder Pattern works the same way!
The Builder Pattern lets you construct complex objects step by step. Instead of passing many parameters to a constructor (which gets messy), you use a builder to set properties one at a time.
What’s the Use of Builder Pattern?
Section titled “What’s the Use of Builder Pattern?”The Builder Pattern is useful when:
- Objects have many optional parameters - Constructor with 10+ parameters is messy
- Complex object construction - Objects need step-by-step setup with validation
- You want readable code - Method chaining makes code self-documenting
- You need different representations - Same construction process, different results
- You want to avoid telescoping constructors - Multiple constructors with different parameters
What Happens If We Don’t Use Builder Pattern?
Section titled “What Happens If We Don’t Use Builder Pattern?”Without the Builder Pattern, you might:
- Create telescoping constructors - Multiple constructors with different parameters
- Use setters everywhere - Objects in incomplete state until all setters called
- Pass many parameters - Constructor with 10+ parameters is hard to read and error-prone
- Lose immutability - Objects can be modified after creation
- Violate Single Responsibility - Object handles both construction and business logic
Simple Example: The Custom Pizza
Section titled “Simple Example: The Custom Pizza”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Builder Pattern works in practice - showing the step-by-step construction:
The Problem
Section titled “The Problem”You’re building a pizza ordering system where customers can customize their pizza with size, crust, sauce, cheese, and toppings. Without Builder Pattern:
# ❌ Without Builder Pattern - Messy constructors!
class Pizza: def __init__(self, size=None, crust=None, sauce=None, cheese=None, pepperoni=False, mushrooms=False, onions=False, peppers=False): self.size = size self.crust = crust self.sauce = sauce self.cheese = cheese self.pepperoni = pepperoni self.mushrooms = mushrooms self.onions = onions self.peppers = peppers
def describe(self): toppings = [] if self.pepperoni: toppings.append("pepperoni") if self.mushrooms: toppings.append("mushrooms") if self.onions: toppings.append("onions") if self.peppers: toppings.append("peppers")
return f"{self.size} {self.crust} pizza with {self.sauce} sauce, {self.cheese} cheese, and {', '.join(toppings) if toppings else 'no'} toppings"
# Problem: Hard to read and error-prone!pizza1 = Pizza("large", "thin", "tomato", "mozzarella", True, False, True, False)pizza2 = Pizza(size="medium", crust="thick", sauce="tomato", cheese="cheddar", pepperoni=False, mushrooms=True, onions=False, peppers=True)
# Problems:# - Hard to remember parameter order# - Easy to mix up parameters# - What if I only want size and crust? Need to pass None for everything else# - Can't validate until object is created// ❌ Without Builder Pattern - Messy constructors!
public class Pizza { private String size; private String crust; private String sauce; private String cheese; private boolean pepperoni; private boolean mushrooms; private boolean onions; private boolean peppers;
// Problem: Constructor with many parameters - hard to read! public Pizza(String size, String crust, String sauce, String cheese, boolean pepperoni, boolean mushrooms, boolean onions, boolean peppers) { this.size = size; this.crust = crust; this.sauce = sauce; this.cheese = cheese; this.pepperoni = pepperoni; this.mushrooms = mushrooms; this.onions = onions; this.peppers = peppers; }
public String describe() { StringBuilder toppings = new StringBuilder(); if (pepperoni) toppings.append("pepperoni "); if (mushrooms) toppings.append("mushrooms "); if (onions) toppings.append("onions "); if (peppers) toppings.append("peppers ");
return size + " " + crust + " pizza with " + sauce + " sauce, " + cheese + " cheese, and " + (toppings.length() > 0 ? toppings.toString() : "no") + "toppings"; }}
// Problem: Hard to read and error-prone!Pizza pizza1 = new Pizza("large", "thin", "tomato", "mozzarella", true, false, true, false);Pizza pizza2 = new Pizza("medium", "thick", "tomato", "cheddar", false, true, false, true);
// Problems:// - Hard to remember parameter order// - Easy to mix up parameters// - What if I only want size and crust? Need multiple constructors// - Can't validate until object is created// ❌ Without Builder Pattern - Messy constructors!
class Pizza { size?: string; crust?: string; sauce?: string; cheese?: string; pepperoni: boolean; mushrooms: boolean; onions: boolean; peppers: boolean;
constructor( size?: string, crust?: string, sauce?: string, cheese?: string, pepperoni: boolean = false, mushrooms: boolean = false, onions: boolean = false, peppers: boolean = false ) { this.size = size; this.crust = crust; this.sauce = sauce; this.cheese = cheese; this.pepperoni = pepperoni; this.mushrooms = mushrooms; this.onions = onions; this.peppers = peppers; }
describe(): string { const toppings: string[] = []; if (this.pepperoni) toppings.push("pepperoni"); if (this.mushrooms) toppings.push("mushrooms"); if (this.onions) toppings.push("onions"); if (this.peppers) toppings.push("peppers");
return `${this.size} ${this.crust} pizza with ${this.sauce} sauce, ${this.cheese} cheese, and ${toppings.length > 0 ? toppings.join(', ') : 'no'} toppings`; }}
// Problem: Hard to read and error-prone!const pizza1 = new Pizza("large", "thin", "tomato", "mozzarella", true, false, true, false);const pizza2 = new Pizza("medium", "thick", "tomato", "cheddar", false, true, false, true);
// Problems:// - Hard to remember parameter order// - Easy to mix up parameters// - What if I only want size and crust? Need to pass undefined for everything else// - Can't validate until object is created// ❌ Without Builder Pattern - Messy constructors!
#include <string>#include <vector>
class Pizza {private: std::string size; std::string crust; std::string sauce; std::string cheese; bool pepperoni; bool mushrooms; bool onions; bool peppers;
public: // Problem: Constructor with many parameters - hard to read! Pizza(std::string size = "", std::string crust = "", std::string sauce = "", std::string cheese = "", bool pepperoni = false, bool mushrooms = false, bool onions = false, bool peppers = false) : size(size), crust(crust), sauce(sauce), cheese(cheese), pepperoni(pepperoni), mushrooms(mushrooms), onions(onions), peppers(peppers) {}
std::string describe() { std::vector<std::string> toppings; if (pepperoni) toppings.push_back("pepperoni"); if (mushrooms) toppings.push_back("mushrooms"); if (onions) toppings.push_back("onions"); if (peppers) toppings.push_back("peppers");
std::string toppingsStr = toppings.empty() ? "no" : ""; for (size_t i = 0; i < toppings.size(); ++i) { toppingsStr += toppings[i]; if (i < toppings.size() - 1) toppingsStr += ", "; }
return size + " " + crust + " pizza with " + sauce + " sauce, " + cheese + " cheese, and " + toppingsStr + " toppings"; }};
// Problem: Hard to read and error-prone!Pizza pizza1("large", "thin", "tomato", "mozzarella", true, false, true, false);Pizza pizza2("medium", "thick", "tomato", "cheddar", false, true, false, true);
// Problems:// - Hard to remember parameter order// - Easy to mix up parameters// - What if I only want size and crust? Need to pass empty strings for everything else// - Can't validate until object is created// ❌ Without Builder Pattern - Messy constructors!
using System;using System.Collections.Generic;using System.Linq;
public class Pizza{ public string Size { get; set; } public string Crust { get; set; } public string Sauce { get; set; } public string Cheese { get; set; } public bool Pepperoni { get; set; } public bool Mushrooms { get; set; } public bool Onions { get; set; } public bool Peppers { get; set; }
// Problem: Constructor with many parameters - hard to read! public Pizza(string size = null, string crust = null, string sauce = null, string cheese = null, bool pepperoni = false, bool mushrooms = false, bool onions = false, bool peppers = false) { Size = size; Crust = crust; Sauce = sauce; Cheese = cheese; Pepperoni = pepperoni; Mushrooms = mushrooms; Onions = onions; Peppers = peppers; }
public string Describe() { var toppings = new List<string>(); if (Pepperoni) toppings.Add("pepperoni"); if (Mushrooms) toppings.Add("mushrooms"); if (Onions) toppings.Add("onions"); if (Peppers) toppings.Add("peppers");
string toppingsStr = toppings.Any() ? string.Join(", ", toppings) : "no"; return $"{Size} {Crust} pizza with {Sauce} sauce, {Cheese} cheese, and {toppingsStr} toppings"; }}
// Problem: Hard to read and error-prone!Pizza pizza1 = new Pizza("large", "thin", "tomato", "mozzarella", true, false, true, false);Pizza pizza2 = new Pizza("medium", "thick", "tomato", "cheddar", false, true, false, true);
// Problems:// - Hard to remember parameter order// - Easy to mix up parameters// - What if I only want size and crust? Need to pass null for everything else// - Can't validate until object is created// ❌ Without Builder Pattern - Messy constructors!
package main
import ( "fmt" "strings")
type Pizza struct { Size, Crust, Sauce, Cheese string Pepperoni, Mushrooms, Onions, Peppers bool}
func NewPizza(size, crust, sauce, cheese string, pepperoni, mushrooms, onions, peppers bool,) *Pizza { return &Pizza{ Size: size, Crust: crust, Sauce: sauce, Cheese: cheese, Pepperoni: pepperoni, Mushrooms: mushrooms, Onions: onions, Peppers: peppers, }}
func (p *Pizza) Describe() string { var toppings []string if p.Pepperoni { toppings = append(toppings, "pepperoni") } if p.Mushrooms { toppings = append(toppings, "mushrooms") } if p.Onions { toppings = append(toppings, "onions") } if p.Peppers { toppings = append(toppings, "peppers") } toppingsStr := "no" if len(toppings) > 0 { toppingsStr = strings.Join(toppings, ", ") } return fmt.Sprintf("%s %s pizza with %s sauce, %s cheese, and %s toppings", p.Size, p.Crust, p.Sauce, p.Cheese, toppingsStr)}
func main() { pizza1 := NewPizza("large", "thin", "tomato", "mozzarella", true, false, true, false) pizza2 := NewPizza("medium", "thick", "tomato", "cheddar", false, true, false, true)
fmt.Println(pizza1.Describe()) fmt.Println(pizza2.Describe())}
// Problems:// - Hard to remember parameter order// - Easy to mix up parameters// - What if I only want size and crust? Still need zero values for everything else// - Can't validate until object is created// The Problemstruct Pizza { size: String, cheese: bool, pepperoni: bool,}impl Pizza { fn new(size: String, cheese: bool, pepperoni: bool) -> Self { Self { size, cheese, pepperoni, } }}Problems:
- Hard to read - What does
True, False, True, Falsemean? - Easy to make mistakes - Wrong parameter order
- Inflexible - Need to pass all parameters even if you don’t need them
- No validation - Can create invalid pizzas
- Not self-documenting - Code doesn’t explain what it’s doing
The Solution: Builder Pattern
Section titled “The Solution: Builder Pattern”Class Structure
Section titled “Class Structure”from typing import List
# Step 1: Create the Product classclass Pizza: def __init__(self): self.size = None self.crust = None self.sauce = None self.cheese = None self.toppings: List[str] = []
def describe(self) -> str: toppings_str = ', '.join(self.toppings) if self.toppings else 'no' return f"{self.size} {self.crust} pizza with {self.sauce} sauce, {self.cheese} cheese, and {toppings_str} toppings"
# Step 2: Create the Builderclass PizzaBuilder: """Builder for constructing Pizza objects step by step"""
def __init__(self): self.pizza = Pizza()
def with_size(self, size: str) -> 'PizzaBuilder': """Set pizza size""" self.pizza.size = size return self # Return self for method chaining
def with_crust(self, crust: str) -> 'PizzaBuilder': """Set crust type""" self.pizza.crust = crust return self
def with_sauce(self, sauce: str) -> 'PizzaBuilder': """Set sauce type""" self.pizza.sauce = sauce return self
def with_cheese(self, cheese: str) -> 'PizzaBuilder': """Set cheese type""" self.pizza.cheese = cheese return self
def add_topping(self, topping: str) -> 'PizzaBuilder': """Add a topping""" self.pizza.toppings.append(topping) return self
def build(self) -> Pizza: """Build and return the pizza""" # Validate before building if not self.pizza.size: raise ValueError("Pizza size is required") if not self.pizza.crust: raise ValueError("Pizza crust is required") if not self.pizza.sauce: raise ValueError("Pizza sauce is required") if not self.pizza.cheese: raise ValueError("Pizza cheese is required")
return self.pizza
# Step 3: Use the builder - clean and readable!def create_pizza(): # Method chaining makes it self-documenting! pizza1 = (PizzaBuilder() .with_size("large") .with_crust("thin") .with_sauce("tomato") .with_cheese("mozzarella") .add_topping("pepperoni") .add_topping("onions") .build())
pizza2 = (PizzaBuilder() .with_size("medium") .with_crust("thick") .with_sauce("tomato") .with_cheese("cheddar") .add_topping("mushrooms") .add_topping("peppers") .build())
print(pizza1.describe()) print(pizza2.describe())
# Benefits:# - Self-documenting code# - Easy to add/remove options# - Validation before building# - Flexible - only set what you needimport java.util.ArrayList;import java.util.List;
// Step 1: Create the Product classpublic class Pizza { private String size; private String crust; private String sauce; private String cheese; private List<String> toppings = new ArrayList<>();
public String describe() { String toppingsStr = toppings.isEmpty() ? "no" : String.join(", ", toppings); return size + " " + crust + " pizza with " + sauce + " sauce, " + cheese + " cheese, and " + toppingsStr + " toppings"; }
// Setters (package-private, only Builder can set) void setSize(String size) { this.size = size; } void setCrust(String crust) { this.crust = crust; } void setSauce(String sauce) { this.sauce = sauce; } void setCheese(String cheese) { this.cheese = cheese; } void addTopping(String topping) { this.toppings.add(topping); }}
// Step 2: Create the Builderpublic class PizzaBuilder { private Pizza pizza = new Pizza();
public PizzaBuilder withSize(String size) { pizza.setSize(size); return this; // Return this for method chaining }
public PizzaBuilder withCrust(String crust) { pizza.setCrust(crust); return this; }
public PizzaBuilder withSauce(String sauce) { pizza.setSauce(sauce); return this; }
public PizzaBuilder withCheese(String cheese) { pizza.setCheese(cheese); return this; }
public PizzaBuilder addTopping(String topping) { pizza.addTopping(topping); return this; }
public Pizza build() { // Validate before building if (pizza.getSize() == null) { throw new IllegalArgumentException("Pizza size is required"); } if (pizza.getCrust() == null) { throw new IllegalArgumentException("Pizza crust is required"); } if (pizza.getSauce() == null) { throw new IllegalArgumentException("Pizza sauce is required"); } if (pizza.getCheese() == null) { throw new IllegalArgumentException("Pizza cheese is required"); }
return pizza; }}
// Step 3: Use the builder - clean and readable!public class PizzaOrder { public static void main(String[] args) { // Method chaining makes it self-documenting! Pizza pizza1 = new PizzaBuilder() .withSize("large") .withCrust("thin") .withSauce("tomato") .withCheese("mozzarella") .addTopping("pepperoni") .addTopping("onions") .build();
Pizza pizza2 = new PizzaBuilder() .withSize("medium") .withCrust("thick") .withSauce("tomato") .withCheese("cheddar") .addTopping("mushrooms") .addTopping("peppers") .build();
System.out.println(pizza1.describe()); System.out.println(pizza2.describe()); }}
// Benefits:// - Self-documenting code// - Easy to add/remove options// - Validation before building// - Flexible - only set what you need// Step 1: Create the Product classclass Pizza { size?: string; crust?: string; sauce?: string; cheese?: string; toppings: string[] = [];
describe(): string { const toppingsStr = this.toppings.length > 0 ? this.toppings.join(', ') : 'no'; return `${this.size} ${this.crust} pizza with ${this.sauce} sauce, ${this.cheese} cheese, and ${toppingsStr} toppings`; }}
// Step 2: Create the Builderclass PizzaBuilder { /** Builder for constructing Pizza objects step by step */ private pizza: Pizza;
constructor() { this.pizza = new Pizza(); }
withSize(size: string): PizzaBuilder { /** Set pizza size */ this.pizza.size = size; return this; // Return this for method chaining }
withCrust(crust: string): PizzaBuilder { /** Set crust type */ this.pizza.crust = crust; return this; }
withSauce(sauce: string): PizzaBuilder { /** Set sauce type */ this.pizza.sauce = sauce; return this; }
withCheese(cheese: string): PizzaBuilder { /** Set cheese type */ this.pizza.cheese = cheese; return this; }
addTopping(topping: string): PizzaBuilder { /** Add a topping */ this.pizza.toppings.push(topping); return this; }
build(): Pizza { /** Build and return the pizza */ // Validate before building if (!this.pizza.size) { throw new Error("Pizza size is required"); } if (!this.pizza.crust) { throw new Error("Pizza crust is required"); } if (!this.pizza.sauce) { throw new Error("Pizza sauce is required"); } if (!this.pizza.cheese) { throw new Error("Pizza cheese is required"); }
return this.pizza; }}
// Step 3: Use the builder - clean and readable!function createPizza(): void { // Method chaining makes it self-documenting! const pizza1 = new PizzaBuilder() .withSize("large") .withCrust("thin") .withSauce("tomato") .withCheese("mozzarella") .addTopping("pepperoni") .addTopping("onions") .build();
const pizza2 = new PizzaBuilder() .withSize("medium") .withCrust("thick") .withSauce("tomato") .withCheese("cheddar") .addTopping("mushrooms") .addTopping("peppers") .build();
console.log(pizza1.describe()); console.log(pizza2.describe());}
// Benefits:// - Self-documenting code// - Easy to add/remove options// - Validation before building// - Flexible - only set what you need#include <string>#include <vector>#include <stdexcept>#include <iostream>
// Step 1: Create the Product classclass Pizza {public: std::string size; std::string crust; std::string sauce; std::string cheese; std::vector<std::string> toppings;
std::string describe() const { std::string toppingsStr; if (toppings.empty()) { toppingsStr = "no"; } else { for (size_t i = 0; i < toppings.size(); ++i) { toppingsStr += toppings[i]; if (i < toppings.size() - 1) toppingsStr += ", "; } }
return size + " " + crust + " pizza with " + sauce + " sauce, " + cheese + " cheese, and " + toppingsStr + " toppings"; }};
// Step 2: Create the Builderclass PizzaBuilder {private: Pizza pizza;
public: /** Builder for constructing Pizza objects step by step */
PizzaBuilder& withSize(const std::string& size) { /** Set pizza size */ pizza.size = size; return *this; // Return *this for method chaining }
PizzaBuilder& withCrust(const std::string& crust) { /** Set crust type */ pizza.crust = crust; return *this; }
PizzaBuilder& withSauce(const std::string& sauce) { /** Set sauce type */ pizza.sauce = sauce; return *this; }
PizzaBuilder& withCheese(const std::string& cheese) { /** Set cheese type */ pizza.cheese = cheese; return *this; }
PizzaBuilder& addTopping(const std::string& topping) { /** Add a topping */ pizza.toppings.push_back(topping); return *this; }
Pizza build() { /** Build and return the pizza */ // Validate before building if (pizza.size.empty()) { throw std::invalid_argument("Pizza size is required"); } if (pizza.crust.empty()) { throw std::invalid_argument("Pizza crust is required"); } if (pizza.sauce.empty()) { throw std::invalid_argument("Pizza sauce is required"); } if (pizza.cheese.empty()) { throw std::invalid_argument("Pizza cheese is required"); }
return pizza; }};
// Step 3: Use the builder - clean and readable!void createPizza() { // Method chaining makes it self-documenting! Pizza pizza1 = PizzaBuilder() .withSize("large") .withCrust("thin") .withSauce("tomato") .withCheese("mozzarella") .addTopping("pepperoni") .addTopping("onions") .build();
Pizza pizza2 = PizzaBuilder() .withSize("medium") .withCrust("thick") .withSauce("tomato") .withCheese("cheddar") .addTopping("mushrooms") .addTopping("peppers") .build();
std::cout << pizza1.describe() << std::endl; std::cout << pizza2.describe() << std::endl;}
// Benefits:// - Self-documenting code// - Easy to add/remove options// - Validation before building// - Flexible - only set what you needusing System;using System.Collections.Generic;using System.Linq;
// Step 1: Create the Product classpublic class Pizza{ public string Size { get; set; } public string Crust { get; set; } public string Sauce { get; set; } public string Cheese { get; set; } public List<string> Toppings { get; set; } = new List<string>();
public string Describe() { string toppingsStr = Toppings.Any() ? string.Join(", ", Toppings) : "no"; return $"{Size} {Crust} pizza with {Sauce} sauce, {Cheese} cheese, and {toppingsStr} toppings"; }}
// Step 2: Create the Builderpublic class PizzaBuilder{ /** Builder for constructing Pizza objects step by step */ private Pizza pizza;
public PizzaBuilder() { pizza = new Pizza(); }
public PizzaBuilder WithSize(string size) { /** Set pizza size */ pizza.Size = size; return this; // Return this for method chaining }
public PizzaBuilder WithCrust(string crust) { /** Set crust type */ pizza.Crust = crust; return this; }
public PizzaBuilder WithSauce(string sauce) { /** Set sauce type */ pizza.Sauce = sauce; return this; }
public PizzaBuilder WithCheese(string cheese) { /** Set cheese type */ pizza.Cheese = cheese; return this; }
public PizzaBuilder AddTopping(string topping) { /** Add a topping */ pizza.Toppings.Add(topping); return this; }
public Pizza Build() { /** Build and return the pizza */ // Validate before building if (string.IsNullOrEmpty(pizza.Size)) { throw new InvalidOperationException("Pizza size is required"); } if (string.IsNullOrEmpty(pizza.Crust)) { throw new InvalidOperationException("Pizza crust is required"); } if (string.IsNullOrEmpty(pizza.Sauce)) { throw new InvalidOperationException("Pizza sauce is required"); } if (string.IsNullOrEmpty(pizza.Cheese)) { throw new InvalidOperationException("Pizza cheese is required"); }
return pizza; }}
// Step 3: Use the builder - clean and readable!class Program{ static void CreatePizza() { // Method chaining makes it self-documenting! Pizza pizza1 = new PizzaBuilder() .WithSize("large") .WithCrust("thin") .WithSauce("tomato") .WithCheese("mozzarella") .AddTopping("pepperoni") .AddTopping("onions") .Build();
Pizza pizza2 = new PizzaBuilder() .WithSize("medium") .WithCrust("thick") .WithSauce("tomato") .WithCheese("cheddar") .AddTopping("mushrooms") .AddTopping("peppers") .Build();
Console.WriteLine(pizza1.Describe()); Console.WriteLine(pizza2.Describe()); }}
// Benefits:// - Self-documenting code// - Easy to add/remove options// - Validation before building// - Flexible - only set what you needpackage main
import ( "fmt" "strings")
// Step 1: Producttype Pizza struct { Size, Crust, Sauce, Cheese string Toppings []string}
func (p *Pizza) Describe() string { toppingsStr := "no" if len(p.Toppings) > 0 { toppingsStr = strings.Join(p.Toppings, ", ") } return fmt.Sprintf("%s %s pizza with %s sauce, %s cheese, and %s toppings", p.Size, p.Crust, p.Sauce, p.Cheese, toppingsStr)}
// Step 2: Buildertype PizzaBuilder struct { pizza *Pizza}
func NewPizzaBuilder() *PizzaBuilder { return &PizzaBuilder{pizza: &Pizza{Toppings: []string{}}}}
func (b *PizzaBuilder) WithSize(size string) *PizzaBuilder { b.pizza.Size = size return b}
func (b *PizzaBuilder) WithCrust(crust string) *PizzaBuilder { b.pizza.Crust = crust return b}
func (b *PizzaBuilder) WithSauce(sauce string) *PizzaBuilder { b.pizza.Sauce = sauce return b}
func (b *PizzaBuilder) WithCheese(cheese string) *PizzaBuilder { b.pizza.Cheese = cheese return b}
func (b *PizzaBuilder) AddTopping(topping string) *PizzaBuilder { b.pizza.Toppings = append(b.pizza.Toppings, topping) return b}
func (b *PizzaBuilder) Build() *Pizza { if b.pizza.Size == "" { panic("Pizza size is required") } if b.pizza.Crust == "" { panic("Pizza crust is required") } if b.pizza.Sauce == "" { panic("Pizza sauce is required") } if b.pizza.Cheese == "" { panic("Pizza cheese is required") } return b.pizza}
func createPizza() { pizza1 := NewPizzaBuilder(). WithSize("large"). WithCrust("thin"). WithSauce("tomato"). WithCheese("mozzarella"). AddTopping("pepperoni"). AddTopping("onions"). Build()
pizza2 := NewPizzaBuilder(). WithSize("medium"). WithCrust("thick"). WithSauce("tomato"). WithCheese("cheddar"). AddTopping("mushrooms"). AddTopping("peppers"). Build()
fmt.Println(pizza1.Describe()) fmt.Println(pizza2.Describe())}
// Benefits:// - Self-documenting code// - Easy to add/remove options// - Validation before building// - Flexible - only set what you need// Class Structure#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}Real-World Software Example: Building HTTP Requests
Section titled “Real-World Software Example: Building HTTP Requests”Now let’s see a realistic software example - building HTTP requests with many optional parameters (headers, query params, body, etc.).
The Problem
Section titled “The Problem”You’re building an HTTP client library. HTTP requests have many optional components: headers, query parameters, request body, authentication, timeout, retries, etc. Without Builder Pattern:
# ❌ Without Builder Pattern - Messy!
class HttpRequest: def __init__(self, method, url, headers=None, params=None, body=None, auth=None, timeout=None, retries=None, follow_redirects=None): self.method = method self.url = url self.headers = headers or {} self.params = params or {} self.body = body self.auth = auth self.timeout = timeout self.retries = retries self.follow_redirects = follow_redirects
def execute(self): # Build and execute HTTP request print(f"Executing {self.method} {self.url}") if self.headers: print(f"Headers: {self.headers}") if self.params: print(f"Params: {self.params}") if self.body: print(f"Body: {self.body}") return {"status": "success"}
# Problem: Hard to read and error-prone!request1 = HttpRequest( "GET", "https://api.example.com/users", headers={"Authorization": "Bearer token123"}, params={"page": "1", "limit": "10"}, timeout=30, retries=3, follow_redirects=True)
# What if I only need method and URL? Still need to pass None for everything!request2 = HttpRequest("GET", "https://api.example.com/ping", None, None, None, None, None, None, None)
# Problems:# - Hard to read - what do all those None values mean?# - Easy to mix up parameter order# - Can't validate until execution# - Not self-documentingimport java.util.Map;import java.util.HashMap;
// ❌ Without Builder Pattern - Messy!
public class HttpRequest { private String method; private String url; private Map<String, String> headers; private Map<String, String> params; private String body; private String auth; private Integer timeout; private Integer retries; private Boolean followRedirects;
// Problem: Constructor with many parameters! public HttpRequest(String method, String url, Map<String, String> headers, Map<String, String> params, String body, String auth, Integer timeout, Integer retries, Boolean followRedirects) { this.method = method; this.url = url; this.headers = headers != null ? headers : new HashMap<>(); this.params = params != null ? params : new HashMap<>(); this.body = body; this.auth = auth; this.timeout = timeout; this.retries = retries; this.followRedirects = followRedirects; }
public Map<String, Object> execute() { // Build and execute HTTP request System.out.println("Executing " + method + " " + url); if (!headers.isEmpty()) { System.out.println("Headers: " + headers); } if (!params.isEmpty()) { System.out.println("Params: " + params); } if (body != null) { System.out.println("Body: " + body); } Map<String, Object> result = new HashMap<>(); result.put("status", "success"); return result; }}
// Problem: Hard to read and error-prone!Map<String, String> headers = new HashMap<>();headers.put("Authorization", "Bearer token123");Map<String, String> params = new HashMap<>();params.put("page", "1");params.put("limit", "10");
HttpRequest request1 = new HttpRequest( "GET", "https://api.example.com/users", headers, params, null, // body null, // auth 30, // timeout 3, // retries true // followRedirects);
// What if I only need method and URL? Still need to pass null for everything!HttpRequest request2 = new HttpRequest("GET", "https://api.example.com/ping", null, null, null, null, null, null, null);
// Problems:// - Hard to read - what do all those null values mean?// - Easy to mix up parameter order// - Can't validate until execution// - Not self-documenting// ❌ Without Builder Pattern - Messy!
class HttpRequest { method: string; url: string; headers: Record<string, string>; params: Record<string, string>; body?: string; auth?: string; timeout?: number; retries?: number; followRedirects?: boolean;
constructor( method: string, url: string, headers?: Record<string, string>, params?: Record<string, string>, body?: string, auth?: string, timeout?: number, retries?: number, followRedirects?: boolean ) { this.method = method; this.url = url; this.headers = headers || {}; this.params = params || {}; this.body = body; this.auth = auth; this.timeout = timeout; this.retries = retries; this.followRedirects = followRedirects; }
execute(): Record<string, string> { // Build and execute HTTP request console.log(`Executing ${this.method} ${this.url}`); if (Object.keys(this.headers).length > 0) { console.log(`Headers: ${JSON.stringify(this.headers)}`); } if (Object.keys(this.params).length > 0) { console.log(`Params: ${JSON.stringify(this.params)}`); } if (this.body) { console.log(`Body: ${this.body}`); } return { status: "success" }; }}
// Problem: Hard to read and error-prone!const request1 = new HttpRequest( "GET", "https://api.example.com/users", { "Authorization": "Bearer token123" }, { "page": "1", "limit": "10" }, undefined, // body undefined, // auth 30, // timeout 3, // retries true // followRedirects);
// What if I only need method and URL? Still need to pass undefined for everything!const request2 = new HttpRequest("GET", "https://api.example.com/ping", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
// Problems:// - Hard to read - what do all those undefined values mean?// - Easy to mix up parameter order// - Can't validate until execution// - Not self-documenting// ❌ Without Builder Pattern - Messy!
#include <string>#include <map>#include <iostream>#include <optional>
class HttpRequest {private: std::string method; std::string url; std::map<std::string, std::string> headers; std::map<std::string, std::string> params; std::optional<std::string> body; std::optional<std::string> auth; std::optional<int> timeout; std::optional<int> retries; std::optional<bool> followRedirects;
public: // Problem: Constructor with many parameters! HttpRequest(std::string method, std::string url, std::map<std::string, std::string> headers = {}, std::map<std::string, std::string> params = {}, std::optional<std::string> body = std::nullopt, std::optional<std::string> auth = std::nullopt, std::optional<int> timeout = std::nullopt, std::optional<int> retries = std::nullopt, std::optional<bool> followRedirects = std::nullopt) : method(method), url(url), headers(headers), params(params), body(body), auth(auth), timeout(timeout), retries(retries), followRedirects(followRedirects) {}
void execute() { // Build and execute HTTP request std::cout << "Executing " << method << " " << url << std::endl; if (!headers.empty()) { std::cout << "Headers: "; for (const auto& [key, value] : headers) { std::cout << key << "=" << value << " "; } std::cout << std::endl; } if (!params.empty()) { std::cout << "Params: "; for (const auto& [key, value] : params) { std::cout << key << "=" << value << " "; } std::cout << std::endl; } if (body.has_value()) { std::cout << "Body: " << body.value() << std::endl; } }};
// Problem: Hard to read and error-prone!HttpRequest request1( "GET", "https://api.example.com/users", {{"Authorization", "Bearer token123"}}, {{"page", "1"}, {"limit", "10"}}, std::nullopt, // body std::nullopt, // auth 30, // timeout 3, // retries true // followRedirects);
// What if I only need method and URL? Still need to pass std::nullopt for everything!HttpRequest request2("GET", "https://api.example.com/ping", {}, {}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt);
// Problems:// - Hard to read - what do all those std::nullopt values mean?// - Easy to mix up parameter order// - Can't validate until execution// - Not self-documenting// ❌ Without Builder Pattern - Messy!
using System;using System.Collections.Generic;
public class HttpRequest{ private string method; private string url; private Dictionary<string, string> headers; private Dictionary<string, string> parameters; private string body; private string auth; private int? timeout; private int? retries; private bool? followRedirects;
// Problem: Constructor with many parameters! public HttpRequest(string method, string url, Dictionary<string, string> headers = null, Dictionary<string, string> parameters = null, string body = null, string auth = null, int? timeout = null, int? retries = null, bool? followRedirects = null) { this.method = method; this.url = url; this.headers = headers ?? new Dictionary<string, string>(); this.parameters = parameters ?? new Dictionary<string, string>(); this.body = body; this.auth = auth; this.timeout = timeout; this.retries = retries; this.followRedirects = followRedirects; }
public Dictionary<string, object> Execute() { // Build and execute HTTP request Console.WriteLine($"Executing {method} {url}"); if (headers.Count > 0) { Console.WriteLine($"Headers: {string.Join(", ", headers)}"); } if (parameters.Count > 0) { Console.WriteLine($"Params: {string.Join(", ", parameters)}"); } if (body != null) { Console.WriteLine($"Body: {body}"); } return new Dictionary<string, object> { { "status", "success" } }; }}
// Problem: Hard to read and error-prone!var headers = new Dictionary<string, string> { { "Authorization", "Bearer token123" } };var parameters = new Dictionary<string, string> { { "page", "1" }, { "limit", "10" } };
HttpRequest request1 = new HttpRequest( "GET", "https://api.example.com/users", headers, parameters, null, // body null, // auth 30, // timeout 3, // retries true // followRedirects);
// What if I only need method and URL? Still need to pass null for everything!HttpRequest request2 = new HttpRequest("GET", "https://api.example.com/ping", null, null, null, null, null, null, null);
// Problems:// - Hard to read - what do all those null values mean?// - Easy to mix up parameter order// - Can't validate until execution// - Not self-documenting// ❌ Without Builder Pattern - Messy!
package main
import ( "fmt")
type HttpRequest struct { Method string URL string Headers map[string]string Params map[string]string Body string Auth string Timeout *int Retries *int FollowRedirects *bool}
func NewHttpRequest(method, url string, headers, params map[string]string, body, auth string, timeout, retries *int, followRedirects *bool,) *HttpRequest { h, p := headers, params if h == nil { h = map[string]string{} } if p == nil { p = map[string]string{} } return &HttpRequest{ Method: method, URL: url, Headers: h, Params: p, Body: body, Auth: auth, Timeout: timeout, Retries: retries, FollowRedirects: followRedirects, }}
func (r *HttpRequest) Execute() map[string]any { fmt.Printf("Executing %s %s\n", r.Method, r.URL) if len(r.Headers) > 0 { fmt.Printf("Headers: %v\n", r.Headers) } if len(r.Params) > 0 { fmt.Printf("Params: %v\n", r.Params) } if r.Body != "" { fmt.Printf("Body: %s\n", r.Body) } return map[string]any{"status": "success"}}
func main() { h := map[string]string{"Authorization": "Bearer token123"} p := map[string]string{"page": "1", "limit": "10"} to30 := 30 re3 := 3 fr := true _ = NewHttpRequest( "GET", "https://api.example.com/users", h, p, "", "", &to30, &re3, &fr, ) _ = NewHttpRequest("GET", "https://api.example.com/ping", nil, nil, "", "", nil, nil, nil, )
// What if I only need method and URL? Still must pass nils for optional slots.}
// Problems:// - Hard to read - many nil pointers// - Easy to mix up parameter order// - Can't validate until execution// - Not self-documenting// The Problemstruct Pizza { size: String, cheese: bool, pepperoni: bool,}impl Pizza { fn new(size: String, cheese: bool, pepperoni: bool) -> Self { Self { size, cheese, pepperoni, } }}Problems:
- Hard to read - Many
None/nullvalues - Error-prone - Easy to mix up parameter order
- Inflexible - Must pass all parameters even if unused
- No validation - Can create invalid requests
- Not self-documenting - Code doesn’t explain itself
The Solution: Builder Pattern
Section titled “The Solution: Builder Pattern”from typing import Dict, Optional
# Step 1: Create the Product classclass HttpRequest: def __init__(self): self.method: Optional[str] = None self.url: Optional[str] = None self.headers: Dict[str, str] = {} self.params: Dict[str, str] = {} self.body: Optional[str] = None self.auth: Optional[str] = None self.timeout: Optional[int] = None self.retries: Optional[int] = None self.follow_redirects: Optional[bool] = None
def execute(self) -> Dict[str, str]: """Execute the HTTP request""" print(f"Executing {self.method} {self.url}") if self.headers: print(f"Headers: {self.headers}") if self.params: print(f"Params: {self.params}") if self.body: print(f"Body: {self.body}") return {"status": "success"}
# Step 2: Create the Builderclass HttpRequestBuilder: """Builder for constructing HttpRequest objects step by step"""
def __init__(self): self.request = HttpRequest()
def with_method(self, method: str) -> 'HttpRequestBuilder': """Set HTTP method""" self.request.method = method.upper() return self
def with_url(self, url: str) -> 'HttpRequestBuilder': """Set request URL""" self.request.url = url return self
def add_header(self, key: str, value: str) -> 'HttpRequestBuilder': """Add a header""" self.request.headers[key] = value return self
def add_param(self, key: str, value: str) -> 'HttpRequestBuilder': """Add a query parameter""" self.request.params[key] = value return self
def with_body(self, body: str) -> 'HttpRequestBuilder': """Set request body""" self.request.body = body return self
def with_auth(self, token: str) -> 'HttpRequestBuilder': """Set authentication token""" self.request.auth = token self.request.headers["Authorization"] = f"Bearer {token}" return self
def with_timeout(self, seconds: int) -> 'HttpRequestBuilder': """Set request timeout""" self.request.timeout = seconds return self
def with_retries(self, count: int) -> 'HttpRequestBuilder': """Set number of retries""" self.request.retries = count return self
def follow_redirects(self, follow: bool = True) -> 'HttpRequestBuilder': """Set whether to follow redirects""" self.request.follow_redirects = follow return self
def build(self) -> HttpRequest: """Build and return the HTTP request""" # Validate before building if not self.request.method: raise ValueError("HTTP method is required") if not self.request.url: raise ValueError("URL is required")
return self.request
# Step 3: Use the builder - clean and readable!def make_requests(): # Complex request - self-documenting! request1 = (HttpRequestBuilder() .with_method("GET") .with_url("https://api.example.com/users") .add_header("Authorization", "Bearer token123") .add_header("Content-Type", "application/json") .add_param("page", "1") .add_param("limit", "10") .with_timeout(30) .with_retries(3) .follow_redirects(True) .build())
# Simple request - only what you need! request2 = (HttpRequestBuilder() .with_method("GET") .with_url("https://api.example.com/ping") .build())
# POST request with body request3 = (HttpRequestBuilder() .with_method("POST") .with_url("https://api.example.com/users") .with_auth("token456") .with_timeout(60) .build())
request1.execute() request2.execute() request3.execute()
# Benefits:# - Self-documenting - reads like English# - Flexible - only set what you need# - Validation before building# - Easy to extend with new optionsimport java.util.HashMap;import java.util.Map;
// Step 1: Create the Product classpublic class HttpRequest { private String method; private String url; private Map<String, String> headers = new HashMap<>(); private Map<String, String> params = new HashMap<>(); private String body; private String auth; private Integer timeout; private Integer retries; private Boolean followRedirects;
public Map<String, String> execute() { // Execute HTTP request System.out.println("Executing " + method + " " + url); if (!headers.isEmpty()) { System.out.println("Headers: " + headers); } if (!params.isEmpty()) { System.out.println("Params: " + params); } if (body != null) { System.out.println("Body: " + body); } Map<String, String> result = new HashMap<>(); result.put("status", "success"); return result; }
// Setters (package-private, only Builder can set) void setMethod(String method) { this.method = method; } void setUrl(String url) { this.url = url; } void addHeader(String key, String value) { this.headers.put(key, value); } void addParam(String key, String value) { this.params.put(key, value); } void setBody(String body) { this.body = body; } void setAuth(String auth) { this.auth = auth; } void setTimeout(Integer timeout) { this.timeout = timeout; } void setRetries(Integer retries) { this.retries = retries; } void setFollowRedirects(Boolean followRedirects) { this.followRedirects = followRedirects; }}
// Step 2: Create the Builderpublic class HttpRequestBuilder { private HttpRequest request = new HttpRequest();
public HttpRequestBuilder withMethod(String method) { request.setMethod(method.toUpperCase()); return this; }
public HttpRequestBuilder withUrl(String url) { request.setUrl(url); return this; }
public HttpRequestBuilder addHeader(String key, String value) { request.addHeader(key, value); return this; }
public HttpRequestBuilder addParam(String key, String value) { request.addParam(key, value); return this; }
public HttpRequestBuilder withBody(String body) { request.setBody(body); return this; }
public HttpRequestBuilder withAuth(String token) { request.setAuth(token); request.addHeader("Authorization", "Bearer " + token); return this; }
public HttpRequestBuilder withTimeout(int seconds) { request.setTimeout(seconds); return this; }
public HttpRequestBuilder withRetries(int count) { request.setRetries(count); return this; }
public HttpRequestBuilder followRedirects(boolean follow) { request.setFollowRedirects(follow); return this; }
public HttpRequest build() { // Validate before building if (request.getMethod() == null) { throw new IllegalArgumentException("HTTP method is required"); } if (request.getUrl() == null) { throw new IllegalArgumentException("URL is required"); }
return request; }}
// Step 3: Use the builder - clean and readable!public class HttpClient { public static void main(String[] args) { // Complex request - self-documenting! HttpRequest request1 = new HttpRequestBuilder() .withMethod("GET") .withUrl("https://api.example.com/users") .addHeader("Authorization", "Bearer token123") .addHeader("Content-Type", "application/json") .addParam("page", "1") .addParam("limit", "10") .withTimeout(30) .withRetries(3) .followRedirects(true) .build();
// Simple request - only what you need! HttpRequest request2 = new HttpRequestBuilder() .withMethod("GET") .withUrl("https://api.example.com/ping") .build();
// POST request with body HttpRequest request3 = new HttpRequestBuilder() .withMethod("POST") .withUrl("https://api.example.com/users") .withAuth("token456") .withTimeout(60) .build();
request1.execute(); request2.execute(); request3.execute(); }}
// Benefits:// - Self-documenting - reads like English// - Flexible - only set what you need// - Validation before building// - Easy to extend with new options// Step 1: Create the Product classclass HttpRequest { method?: string; url?: string; headers: Record<string, string> = {}; params: Record<string, string> = {}; body?: string; auth?: string; timeout?: number; retries?: number; followRedirects?: boolean;
execute(): Record<string, string> { /** Execute the HTTP request */ console.log(`Executing ${this.method} ${this.url}`); if (Object.keys(this.headers).length > 0) { console.log(`Headers: ${JSON.stringify(this.headers)}`); } if (Object.keys(this.params).length > 0) { console.log(`Params: ${JSON.stringify(this.params)}`); } if (this.body) { console.log(`Body: ${this.body}`); } return { status: "success" }; }}
// Step 2: Create the Builderclass HttpRequestBuilder { /** Builder for constructing HttpRequest objects step by step */ private request: HttpRequest;
constructor() { this.request = new HttpRequest(); }
withMethod(method: string): HttpRequestBuilder { /** Set HTTP method */ this.request.method = method.toUpperCase(); return this; }
withUrl(url: string): HttpRequestBuilder { /** Set request URL */ this.request.url = url; return this; }
addHeader(key: string, value: string): HttpRequestBuilder { /** Add a header */ this.request.headers[key] = value; return this; }
addParam(key: string, value: string): HttpRequestBuilder { /** Add a query parameter */ this.request.params[key] = value; return this; }
withBody(body: string): HttpRequestBuilder { /** Set request body */ this.request.body = body; return this; }
withAuth(token: string): HttpRequestBuilder { /** Set authentication token */ this.request.auth = token; this.request.headers["Authorization"] = `Bearer ${token}`; return this; }
withTimeout(seconds: number): HttpRequestBuilder { /** Set request timeout */ this.request.timeout = seconds; return this; }
withRetries(count: number): HttpRequestBuilder { /** Set number of retries */ this.request.retries = count; return this; }
followRedirects(follow: boolean = true): HttpRequestBuilder { /** Set whether to follow redirects */ this.request.followRedirects = follow; return this; }
build(): HttpRequest { /** Build and return the HTTP request */ // Validate before building if (!this.request.method) { throw new Error("HTTP method is required"); } if (!this.request.url) { throw new Error("URL is required"); }
return this.request; }}
// Step 3: Use the builder - clean and readable!function makeRequests(): void { // Complex request - self-documenting! const request1 = new HttpRequestBuilder() .withMethod("GET") .withUrl("https://api.example.com/users") .addHeader("Authorization", "Bearer token123") .addHeader("Content-Type", "application/json") .addParam("page", "1") .addParam("limit", "10") .withTimeout(30) .withRetries(3) .followRedirects(true) .build();
// Simple request - only what you need! const request2 = new HttpRequestBuilder() .withMethod("GET") .withUrl("https://api.example.com/ping") .build();
// POST request with body const request3 = new HttpRequestBuilder() .withMethod("POST") .withUrl("https://api.example.com/users") .withAuth("token456") .withTimeout(60) .build();
request1.execute(); request2.execute(); request3.execute();}
// Benefits:// - Self-documenting - reads like English// - Flexible - only set what you need// - Validation before building// - Easy to extend with new options#include <string>#include <map>#include <optional>#include <stdexcept>#include <iostream>#include <algorithm>
// Step 1: Create the Product classclass HttpRequest {public: std::optional<std::string> method; std::optional<std::string> url; std::map<std::string, std::string> headers; std::map<std::string, std::string> params; std::optional<std::string> body; std::optional<std::string> auth; std::optional<int> timeout; std::optional<int> retries; std::optional<bool> followRedirects;
std::map<std::string, std::string> execute() { /** Execute the HTTP request */ std::cout << "Executing " << method.value() << " " << url.value() << std::endl; if (!headers.empty()) { std::cout << "Headers: "; for (const auto& [key, value] : headers) { std::cout << key << "=" << value << " "; } std::cout << std::endl; } if (!params.empty()) { std::cout << "Params: "; for (const auto& [key, value] : params) { std::cout << key << "=" << value << " "; } std::cout << std::endl; } if (body.has_value()) { std::cout << "Body: " << body.value() << std::endl; } return {{"status", "success"}}; }};
// Step 2: Create the Builderclass HttpRequestBuilder {private: HttpRequest request;
public: /** Builder for constructing HttpRequest objects step by step */
HttpRequestBuilder& withMethod(const std::string& method) { /** Set HTTP method */ std::string upperMethod = method; std::transform(upperMethod.begin(), upperMethod.end(), upperMethod.begin(), ::toupper); request.method = upperMethod; return *this; }
HttpRequestBuilder& withUrl(const std::string& url) { /** Set request URL */ request.url = url; return *this; }
HttpRequestBuilder& addHeader(const std::string& key, const std::string& value) { /** Add a header */ request.headers[key] = value; return *this; }
HttpRequestBuilder& addParam(const std::string& key, const std::string& value) { /** Add a query parameter */ request.params[key] = value; return *this; }
HttpRequestBuilder& withBody(const std::string& body) { /** Set request body */ request.body = body; return *this; }
HttpRequestBuilder& withAuth(const std::string& token) { /** Set authentication token */ request.auth = token; request.headers["Authorization"] = "Bearer " + token; return *this; }
HttpRequestBuilder& withTimeout(int seconds) { /** Set request timeout */ request.timeout = seconds; return *this; }
HttpRequestBuilder& withRetries(int count) { /** Set number of retries */ request.retries = count; return *this; }
HttpRequestBuilder& followRedirects(bool follow = true) { /** Set whether to follow redirects */ request.followRedirects = follow; return *this; }
HttpRequest build() { /** Build and return the HTTP request */ // Validate before building if (!request.method.has_value()) { throw std::invalid_argument("HTTP method is required"); } if (!request.url.has_value()) { throw std::invalid_argument("URL is required"); }
return request; }};
// Step 3: Use the builder - clean and readable!void makeRequests() { // Complex request - self-documenting! HttpRequest request1 = HttpRequestBuilder() .withMethod("GET") .withUrl("https://api.example.com/users") .addHeader("Authorization", "Bearer token123") .addHeader("Content-Type", "application/json") .addParam("page", "1") .addParam("limit", "10") .withTimeout(30) .withRetries(3) .followRedirects(true) .build();
// Simple request - only what you need! HttpRequest request2 = HttpRequestBuilder() .withMethod("GET") .withUrl("https://api.example.com/ping") .build();
// POST request with body HttpRequest request3 = HttpRequestBuilder() .withMethod("POST") .withUrl("https://api.example.com/users") .withAuth("token456") .withTimeout(60) .build();
request1.execute(); request2.execute(); request3.execute();}
// Benefits:// - Self-documenting - reads like English// - Flexible - only set what you need// - Validation before building// - Easy to extend with new optionsusing System;using System.Collections.Generic;
// Step 1: Create the Product classpublic class HttpRequest{ public string Method { get; set; } public string Url { get; set; } public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>(); public Dictionary<string, string> Params { get; set; } = new Dictionary<string, string>(); public string Body { get; set; } public string Auth { get; set; } public int? Timeout { get; set; } public int? Retries { get; set; } public bool? FollowRedirects { get; set; }
public Dictionary<string, string> Execute() { /** Execute the HTTP request */ Console.WriteLine($"Executing {Method} {Url}"); if (Headers.Count > 0) { Console.WriteLine($"Headers: {string.Join(", ", Headers)}"); } if (Params.Count > 0) { Console.WriteLine($"Params: {string.Join(", ", Params)}"); } if (Body != null) { Console.WriteLine($"Body: {Body}"); } return new Dictionary<string, string> { { "status", "success" } }; }}
// Step 2: Create the Builderpublic class HttpRequestBuilder{ /** Builder for constructing HttpRequest objects step by step */ private HttpRequest request;
public HttpRequestBuilder() { request = new HttpRequest(); }
public HttpRequestBuilder WithMethod(string method) { /** Set HTTP method */ request.Method = method.ToUpper(); return this; }
public HttpRequestBuilder WithUrl(string url) { /** Set request URL */ request.Url = url; return this; }
public HttpRequestBuilder AddHeader(string key, string value) { /** Add a header */ request.Headers[key] = value; return this; }
public HttpRequestBuilder AddParam(string key, string value) { /** Add a query parameter */ request.Params[key] = value; return this; }
public HttpRequestBuilder WithBody(string body) { /** Set request body */ request.Body = body; return this; }
public HttpRequestBuilder WithAuth(string token) { /** Set authentication token */ request.Auth = token; request.Headers["Authorization"] = $"Bearer {token}"; return this; }
public HttpRequestBuilder WithTimeout(int seconds) { /** Set request timeout */ request.Timeout = seconds; return this; }
public HttpRequestBuilder WithRetries(int count) { /** Set number of retries */ request.Retries = count; return this; }
public HttpRequestBuilder FollowRedirects(bool follow = true) { /** Set whether to follow redirects */ request.FollowRedirects = follow; return this; }
public HttpRequest Build() { /** Build and return the HTTP request */ // Validate before building if (string.IsNullOrEmpty(request.Method)) { throw new InvalidOperationException("HTTP method is required"); } if (string.IsNullOrEmpty(request.Url)) { throw new InvalidOperationException("URL is required"); }
return request; }}
// Step 3: Use the builder - clean and readable!class Program{ static void MakeRequests() { // Complex request - self-documenting! HttpRequest request1 = new HttpRequestBuilder() .WithMethod("GET") .WithUrl("https://api.example.com/users") .AddHeader("Authorization", "Bearer token123") .AddHeader("Content-Type", "application/json") .AddParam("page", "1") .AddParam("limit", "10") .WithTimeout(30) .WithRetries(3) .FollowRedirects(true) .Build();
// Simple request - only what you need! HttpRequest request2 = new HttpRequestBuilder() .WithMethod("GET") .WithUrl("https://api.example.com/ping") .Build();
// POST request with body HttpRequest request3 = new HttpRequestBuilder() .WithMethod("POST") .WithUrl("https://api.example.com/users") .WithAuth("token456") .WithTimeout(60) .Build();
request1.Execute(); request2.Execute(); request3.Execute(); }}
// Benefits:// - Self-documenting - reads like English// - Flexible - only set what you need// - Validation before building// - Easy to extend with new optionspackage main
import ( "fmt" "strings")
// Step 1: Producttype HttpRequest struct { Method string URL string Headers map[string]string Params map[string]string Body string Auth string Timeout *int Retries *int FollowRedirects *bool}
func newHttpRequest() *HttpRequest { return &HttpRequest{ Headers: map[string]string{}, Params: map[string]string{}, }}
func (r *HttpRequest) Execute() map[string]string { fmt.Printf("Executing %s %s\n", r.Method, r.URL) if len(r.Headers) > 0 { fmt.Printf("Headers: %v\n", r.Headers) } if len(r.Params) > 0 { fmt.Printf("Params: %v\n", r.Params) } if r.Body != "" { fmt.Printf("Body: %s\n", r.Body) } return map[string]string{"status": "success"}}
// Step 2: Buildertype HttpRequestBuilder struct { request *HttpRequest}
func NewHttpRequestBuilder() *HttpRequestBuilder { return &HttpRequestBuilder{request: newHttpRequest()}}
func (b *HttpRequestBuilder) WithMethod(method string) *HttpRequestBuilder { b.request.Method = strings.ToUpper(method) return b}
func (b *HttpRequestBuilder) WithURL(url string) *HttpRequestBuilder { b.request.URL = url return b}
func (b *HttpRequestBuilder) AddHeader(key, value string) *HttpRequestBuilder { b.request.Headers[key] = value return b}
func (b *HttpRequestBuilder) AddParam(key, value string) *HttpRequestBuilder { b.request.Params[key] = value return b}
func (b *HttpRequestBuilder) WithBody(body string) *HttpRequestBuilder { b.request.Body = body return b}
func (b *HttpRequestBuilder) WithAuth(token string) *HttpRequestBuilder { b.request.Auth = token b.request.Headers["Authorization"] = "Bearer " + token return b}
func (b *HttpRequestBuilder) WithTimeout(seconds int) *HttpRequestBuilder { v := seconds b.request.Timeout = &v return b}
func (b *HttpRequestBuilder) WithRetries(count int) *HttpRequestBuilder { v := count b.request.Retries = &v return b}
func (b *HttpRequestBuilder) FollowRedirects(follow bool) *HttpRequestBuilder { b.request.FollowRedirects = &follow return b}
func (b *HttpRequestBuilder) Build() *HttpRequest { if b.request.Method == "" { panic("HTTP method is required") } if b.request.URL == "" { panic("URL is required") } return b.request}
func makeRequests() { request1 := NewHttpRequestBuilder(). WithMethod("GET"). WithURL("https://api.example.com/users"). AddHeader("Authorization", "Bearer token123"). AddHeader("Content-Type", "application/json"). AddParam("page", "1"). AddParam("limit", "10"). WithTimeout(30). WithRetries(3). FollowRedirects(true). Build()
request2 := NewHttpRequestBuilder(). WithMethod("GET"). WithURL("https://api.example.com/ping"). Build()
request3 := NewHttpRequestBuilder(). WithMethod("POST"). WithURL("https://api.example.com/users"). WithAuth("token456"). WithTimeout(60). Build()
request1.Execute() request2.Execute() request3.Execute()}
// Benefits:// - Self-documenting - reads like English// - Flexible - only set what you need// - Validation before building// - Easy to extend with new options// The Solution: Builder Pattern#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}Adding a New Option
Section titled “Adding a New Option”With Builder Pattern, adding a new option is super easy:
# Step 1: Add field to HttpRequestclass HttpRequest: def __init__(self): # ... existing fields ... self.verify_ssl: Optional[bool] = None # New field
# Step 2: Add method to Builder (only change needed!)class HttpRequestBuilder: # ... existing methods ...
def verify_ssl(self, verify: bool = True) -> 'HttpRequestBuilder': """Set SSL verification""" self.request.verify_ssl = verify return self
# Usage - automatically works!request = (HttpRequestBuilder() .with_method("GET") .with_url("https://api.example.com/data") .verify_ssl(False) # New option! .build())
# That's it! No changes needed to existing code!// Step 1: Add field to HttpRequestpublic class HttpRequest { // ... existing fields ... private Boolean verifySsl; // New field
void setVerifySsl(Boolean verifySsl) { this.verifySsl = verifySsl; }}
// Step 2: Add method to Builder (only change needed!)public class HttpRequestBuilder { // ... existing methods ...
public HttpRequestBuilder verifySsl(boolean verify) { request.setVerifySsl(verify); return this; }}
// Usage - automatically works!HttpRequest request = new HttpRequestBuilder() .withMethod("GET") .withUrl("https://api.example.com/data") .verifySsl(false) // New option! .build();
// That's it! No changes needed to existing code!// Step 1: Add field to HttpRequestclass HttpRequest { // ... existing fields ... verifySsl?: boolean; // New field}
// Step 2: Add method to Builder (only change needed!)class HttpRequestBuilder { // ... existing methods ...
verifySsl(verify: boolean = true): HttpRequestBuilder { /** Set SSL verification */ this.request.verifySsl = verify; return this; }}
// Usage - automatically works!const request = new HttpRequestBuilder() .withMethod("GET") .withUrl("https://api.example.com/data") .verifySsl(false) // New option! .build();
// That's it! No changes needed to existing code!// Step 1: Add field to HttpRequestclass HttpRequest { // ... existing fields ... std::optional<bool> verifySsl; // New field};
// Step 2: Add method to Builder (only change needed!)class HttpRequestBuilder { // ... existing methods ...
HttpRequestBuilder& verifySsl(bool verify = true) { /** Set SSL verification */ request.verifySsl = verify; return *this; }};
// Usage - automatically works!HttpRequest request = HttpRequestBuilder() .withMethod("GET") .withUrl("https://api.example.com/data") .verifySsl(false) // New option! .build();
// That's it! No changes needed to existing code!// Step 1: Add field to HttpRequestpublic class HttpRequest{ // ... existing fields ... public bool? VerifySsl { get; set; } // New field}
// Step 2: Add method to Builder (only change needed!)public class HttpRequestBuilder{ // ... existing methods ...
public HttpRequestBuilder VerifySsl(bool verify = true) { /** Set SSL verification */ request.VerifySsl = verify; return this; }}
// Usage - automatically works!HttpRequest request = new HttpRequestBuilder() .WithMethod("GET") .WithUrl("https://api.example.com/data") .VerifySsl(false) // New option! .Build();
// That's it! No changes needed to existing code!// Step 1: Add field to HttpRequesttype HttpRequest struct { VerifySsl *bool}
// Step 2: Add method to Builder (only change needed!)type HttpRequestBuilder struct { request *HttpRequest}
func (b *HttpRequestBuilder) VerifySsl(verify bool) *HttpRequestBuilder { v := verify b.request.VerifySsl = &v return b}
// Usage - automatically works!request := NewHttpRequestBuilder(). WithMethod("GET"). WithURL("https://api.example.com/data"). VerifySsl(false). Build()
_ = request // avoid unused
// That's it! No changes needed to existing code!// Adding a New Option#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}Builder Pattern Variants
Section titled “Builder Pattern Variants”There are several variations of the Builder Pattern:
1. Standard Builder (What We Used)
Section titled “1. Standard Builder (What We Used)”The classic builder with method chaining:
class PizzaBuilder: def with_size(self, size: str) -> 'PizzaBuilder': self.pizza.size = size return self # Method chaining
def build(self) -> Pizza: return self.pizza
# Usagepizza = PizzaBuilder().with_size("large").with_crust("thin").build()public class PizzaBuilder { public PizzaBuilder withSize(String size) { pizza.setSize(size); return this; // Method chaining }
public Pizza build() { return pizza; }}
// UsagePizza pizza = new PizzaBuilder().withSize("large").withCrust("thin").build();class PizzaBuilder { withSize(size: string): PizzaBuilder { this.pizza.size = size; return this; // Method chaining }
build(): Pizza { return this.pizza; }}
// Usageconst pizza = new PizzaBuilder().withSize("large").withCrust("thin").build();class PizzaBuilder {public: PizzaBuilder& withSize(const std::string& size) { pizza.size = size; return *this; // Method chaining }
Pizza build() { return pizza; }};
// UsagePizza pizza = PizzaBuilder().withSize("large").withCrust("thin").build();public class PizzaBuilder{ public PizzaBuilder WithSize(string size) { pizza.Size = size; return this; // Method chaining }
public Pizza Build() { return pizza; }}
// UsagePizza pizza = new PizzaBuilder().WithSize("large").WithCrust("thin").Build();type Pizza struct { Size, Crust string}
type PizzaBuilder struct { pizza *Pizza}
func NewPizzaBuilder() *PizzaBuilder { return &PizzaBuilder{pizza: &Pizza{}}}
func (b *PizzaBuilder) WithSize(size string) *PizzaBuilder { b.pizza.Size = size return b // Method chaining}
func (b *PizzaBuilder) WithCrust(crust string) *PizzaBuilder { b.pizza.Crust = crust return b}
func (b *PizzaBuilder) Build() *Pizza { return b.pizza}
// Usage// pizza := NewPizzaBuilder().WithSize("large").WithCrust("thin").Build()// 1. Standard Builder What We Used#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}2. Fluent Builder with Validation
Section titled “2. Fluent Builder with Validation”Builder that validates at each step:
class PizzaBuilder: def with_size(self, size: str) -> 'PizzaBuilder': if size not in ["small", "medium", "large"]: raise ValueError(f"Invalid size: {size}") self.pizza.size = size return self
def build(self) -> Pizza: # Final validation if not self.pizza.size: raise ValueError("Size is required") return self.pizzapublic class PizzaBuilder { public PizzaBuilder withSize(String size) { if (!Arrays.asList("small", "medium", "large").contains(size)) { throw new IllegalArgumentException("Invalid size: " + size); } pizza.setSize(size); return this; }
public Pizza build() { // Final validation if (pizza.getSize() == null) { throw new IllegalStateException("Size is required"); } return pizza; }}class PizzaBuilder { withSize(size: string): PizzaBuilder { if (!["small", "medium", "large"].includes(size)) { throw new Error(`Invalid size: ${size}`); } this.pizza.size = size; return this; }
build(): Pizza { // Final validation if (!this.pizza.size) { throw new Error("Size is required"); } return this.pizza; }}#include <vector>#include <string>#include <algorithm>#include <stdexcept>
class PizzaBuilder {public: PizzaBuilder& withSize(const std::string& size) { std::vector<std::string> validSizes = {"small", "medium", "large"}; if (std::find(validSizes.begin(), validSizes.end(), size) == validSizes.end()) { throw std::invalid_argument("Invalid size: " + size); } pizza.size = size; return *this; }
Pizza build() { // Final validation if (!pizza.size.has_value()) { throw std::invalid_argument("Size is required"); } return pizza; }};using System;using System.Linq;
public class PizzaBuilder{ public PizzaBuilder WithSize(string size) { var validSizes = new[] { "small", "medium", "large" }; if (!validSizes.Contains(size)) { throw new ArgumentException($"Invalid size: {size}"); } pizza.Size = size; return this; }
public Pizza Build() { // Final validation if (string.IsNullOrEmpty(pizza.Size)) { throw new InvalidOperationException("Size is required"); } return pizza; }}type Pizza struct { Size string}
type PizzaBuilder struct { pizza *Pizza}
func (b *PizzaBuilder) WithSize(size string) *PizzaBuilder { valid := map[string]struct{}{"small": {}, "medium": {}, "large": {}} if _, ok := valid[size]; !ok { panic("Invalid size: " + size) } b.pizza.Size = size return b}
func (b *PizzaBuilder) Build() *Pizza { if b.pizza.Size == "" { panic("Size is required") } return b.pizza}// 2. Fluent Builder with Validation#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}3. Director Pattern (Optional)
Section titled “3. Director Pattern (Optional)”A director that uses the builder to create predefined configurations:
class PizzaDirector: """Director that creates predefined pizza configurations"""
def __init__(self, builder: PizzaBuilder): self.builder = builder
def build_margherita(self) -> Pizza: """Build a standard Margherita pizza""" return (self.builder .with_size("large") .with_crust("thin") .with_sauce("tomato") .with_cheese("mozzarella") .build())
def build_pepperoni(self) -> Pizza: """Build a standard Pepperoni pizza""" return (self.builder .with_size("large") .with_crust("thick") .with_sauce("tomato") .with_cheese("mozzarella") .add_topping("pepperoni") .build())
# Usagebuilder = PizzaBuilder()director = PizzaDirector(builder)margherita = director.build_margherita()public class PizzaDirector { /** * Director that creates predefined pizza configurations */ private PizzaBuilder builder;
public PizzaDirector(PizzaBuilder builder) { this.builder = builder; }
public Pizza buildMargherita() { // Build a standard Margherita pizza return builder .withSize("large") .withCrust("thin") .withSauce("tomato") .withCheese("mozzarella") .build(); }
public Pizza buildPepperoni() { // Build a standard Pepperoni pizza return builder .withSize("large") .withCrust("thick") .withSauce("tomato") .withCheese("mozzarella") .addTopping("pepperoni") .build(); }}
// UsagePizzaBuilder builder = new PizzaBuilder();PizzaDirector director = new PizzaDirector(builder);Pizza margherita = director.buildMargherita();class PizzaDirector { /** Director that creates predefined pizza configurations */ private builder: PizzaBuilder;
constructor(builder: PizzaBuilder) { this.builder = builder; }
buildMargherita(): Pizza { /** Build a standard Margherita pizza */ return this.builder .withSize("large") .withCrust("thin") .withSauce("tomato") .withCheese("mozzarella") .build(); }
buildPepperoni(): Pizza { /** Build a standard Pepperoni pizza */ return this.builder .withSize("large") .withCrust("thick") .withSauce("tomato") .withCheese("mozzarella") .addTopping("pepperoni") .build(); }}
// Usageconst builder = new PizzaBuilder();const director = new PizzaDirector(builder);const margherita = director.buildMargherita();class PizzaDirector {private: PizzaBuilder& builder;
public: /** Director that creates predefined pizza configurations */ PizzaDirector(PizzaBuilder& builder) : builder(builder) {}
Pizza buildMargherita() { /** Build a standard Margherita pizza */ return builder .withSize("large") .withCrust("thin") .withSauce("tomato") .withCheese("mozzarella") .build(); }
Pizza buildPepperoni() { /** Build a standard Pepperoni pizza */ return builder .withSize("large") .withCrust("thick") .withSauce("tomato") .withCheese("mozzarella") .addTopping("pepperoni") .build(); }};
// UsagePizzaBuilder builder;PizzaDirector director(builder);Pizza margherita = director.buildMargherita();public class PizzaDirector{ /** Director that creates predefined pizza configurations */ private PizzaBuilder builder;
public PizzaDirector(PizzaBuilder builder) { this.builder = builder; }
public Pizza BuildMargherita() { /** Build a standard Margherita pizza */ return builder .WithSize("large") .WithCrust("thin") .WithSauce("tomato") .WithCheese("mozzarella") .Build(); }
public Pizza BuildPepperoni() { /** Build a standard Pepperoni pizza */ return builder .WithSize("large") .WithCrust("thick") .WithSauce("tomato") .WithCheese("mozzarella") .AddTopping("pepperoni") .Build(); }}
// UsagePizzaBuilder builder = new PizzaBuilder();PizzaDirector director = new PizzaDirector(builder);Pizza margherita = director.BuildMargherita();type Pizza struct { Size, Crust, Sauce, Cheese string Toppings []string}
type PizzaBuilder struct { pizza *Pizza}
func NewPizzaBuilder() *PizzaBuilder { return &PizzaBuilder{pizza: &Pizza{Toppings: []string{}}}}
func (b *PizzaBuilder) WithSize(v string) *PizzaBuilder { b.pizza.Size = v; return b }func (b *PizzaBuilder) WithCrust(v string) *PizzaBuilder { b.pizza.Crust = v; return b }func (b *PizzaBuilder) WithSauce(v string) *PizzaBuilder { b.pizza.Sauce = v; return b }func (b *PizzaBuilder) WithCheese(v string) *PizzaBuilder { b.pizza.Cheese = v return b}func (b *PizzaBuilder) AddTopping(v string) *PizzaBuilder { b.pizza.Toppings = append(b.pizza.Toppings, v) return b}func (b *PizzaBuilder) Build() *Pizza { return b.pizza}
type PizzaDirector struct { builder *PizzaBuilder}
func NewPizzaDirector(builder *PizzaBuilder) *PizzaDirector { return &PizzaDirector{builder: builder}}
func (d *PizzaDirector) BuildMargherita() *Pizza { return d.builder. WithSize("large").WithCrust("thin"). WithSauce("tomato").WithCheese("mozzarella"). Build()}
func (d *PizzaDirector) BuildPepperoni() *Pizza { return d.builder. WithSize("large").WithCrust("thick"). WithSauce("tomato").WithCheese("mozzarella"). AddTopping("pepperoni"). Build()}
// Usage// director := NewPizzaDirector(NewPizzaBuilder())// margherita := director.BuildMargherita()// 3. Director Pattern Optional#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}When to Use Builder Pattern?
Section titled “When to Use Builder Pattern?”Use Builder Pattern when:
✅ Objects have many optional parameters - 5+ optional parameters
✅ Complex object construction - Objects need step-by-step setup
✅ You want readable code - Method chaining is self-documenting
✅ You need validation - Validate before object creation
✅ You want immutability - Build once, use forever
✅ You’re avoiding telescoping constructors - Multiple constructors are messy
When NOT to Use Builder Pattern?
Section titled “When NOT to Use Builder Pattern?”Don’t use Builder Pattern when:
❌ Simple objects - If object has 1-2 parameters, direct construction is fine
❌ All parameters required - If all parameters are mandatory, constructor is simpler
❌ Performance is critical - Builder adds small overhead (usually negligible)
❌ Over-engineering - Don’t use for simple cases
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Using Builder for Simple Cases
Section titled “Mistake 1: Using Builder for Simple Cases”# ❌ Don't do this for simple cases!class Point: def __init__(self, x: float, y: float): self.x = x self.y = y
class PointBuilder: def with_x(self, x: float): self.point.x = x return self
def with_y(self, y: float): self.point.y = y return self
def build(self): return self.point
# ✅ Better: Direct construction for simple casespoint = Point(10, 20) # Simple and clear!// ❌ Don't do this for simple cases!public class Point { private double x; private double y;
public Point(double x, double y) { this.x = x; this.y = y; }}
public class PointBuilder { public PointBuilder withX(double x) { ... } public PointBuilder withY(double y) { ... } public Point build() { ... }}
// ✅ Better: Direct construction for simple casesPoint point = new Point(10, 20); // Simple and clear!// ❌ Don't do this for simple cases!class Point { constructor(public x: number, public y: number) {}}
class PointBuilder { private point: Point;
withX(x: number): PointBuilder { this.point.x = x; return this; }
withY(y: number): PointBuilder { this.point.y = y; return this; }
build(): Point { return this.point; }}
// ✅ Better: Direct construction for simple casesconst point = new Point(10, 20); // Simple and clear!// ❌ Don't do this for simple cases!class Point {public: double x; double y;
Point(double x, double y) : x(x), y(y) {}};
class PointBuilder {public: PointBuilder& withX(double x) { /* ... */ return *this; } PointBuilder& withY(double y) { /* ... */ return *this; } Point build() { /* ... */ }};
// ✅ Better: Direct construction for simple casesPoint point(10, 20); // Simple and clear!// ❌ Don't do this for simple cases!public class Point{ public double X { get; set; } public double Y { get; set; }
public Point(double x, double y) { X = x; Y = y; }}
public class PointBuilder{ public PointBuilder WithX(double x) { /* ... */ return this; } public PointBuilder WithY(double y) { /* ... */ return this; } public Point Build() { /* ... */ }}
// ✅ Better: Direct construction for simple casesPoint point = new Point(10, 20); // Simple and clear!// ❌ Don't do this for simple cases!type Point struct { X, Y float64}
type PointBuilder struct { point *Point}
func (b *PointBuilder) WithX(x float64) *PointBuilder { b.point.X = x; return b }
func (b *PointBuilder) WithY(y float64) *PointBuilder { b.point.Y = y; return b }
func (b *PointBuilder) Build() *Point { return b.point}
// ✅ Better: direct construction for simple casesfunc main() { _ = &Point{X: 10, Y: 20}}// Mistake 1: Using Builder for Simple Casesstruct Pizza { size: String, cheese: bool, pepperoni: bool,}impl Pizza { fn new(size: String, cheese: bool, pepperoni: bool) -> Self { Self { size, cheese, pepperoni, } }}Mistake 2: Builder Modifying Built Object
Section titled “Mistake 2: Builder Modifying Built Object”# ❌ Builder modifying already-built objectclass BadBuilder: def build(self) -> Pizza: pizza = Pizza() # ... set properties ... return pizza
def modify_pizza(self, pizza: Pizza): pizza.size = "large" # Modifying built object!
# ✅ Better: Builder creates new object each timeclass GoodBuilder: def build(self) -> Pizza: pizza = Pizza() # ... set properties ... return pizza # New object each time// ❌ Builder modifying already-built objectpublic class BadBuilder { public Pizza build() { Pizza pizza = new Pizza(); // ... set properties ... return pizza; }
public void modifyPizza(Pizza pizza) { pizza.setSize("large"); // Modifying built object! }}
// ✅ Better: Builder creates new object each timepublic class GoodBuilder { public Pizza build() { Pizza pizza = new Pizza(); // ... set properties ... return pizza; // New object each time }}// ❌ Builder modifying already-built objectclass BadBuilder { build(): Pizza { const pizza = new Pizza(); // ... set properties ... return pizza; }
modifyPizza(pizza: Pizza): void { pizza.size = "large"; // Modifying built object! }}
// ✅ Better: Builder creates new object each timeclass GoodBuilder { build(): Pizza { const pizza = new Pizza(); // ... set properties ... return pizza; // New object each time }}// ❌ Builder modifying already-built objectclass BadBuilder {public: Pizza build() { Pizza pizza; // ... set properties ... return pizza; }
void modifyPizza(Pizza& pizza) { pizza.size = "large"; // Modifying built object! }};
// ✅ Better: Builder creates new object each timeclass GoodBuilder {public: Pizza build() { Pizza pizza; // ... set properties ... return pizza; // New object each time }};// ❌ Builder modifying already-built objectpublic class BadBuilder{ public Pizza Build() { Pizza pizza = new Pizza(); // ... set properties ... return pizza; }
public void ModifyPizza(Pizza pizza) { pizza.Size = "large"; // Modifying built object! }}
// ✅ Better: Builder creates new object each timepublic class GoodBuilder{ public Pizza Build() { Pizza pizza = new Pizza(); // ... set properties ... return pizza; // New object each time }}type Pizza struct { Size string}
// ❌ Builder modifying already-built objecttype BadBuilder struct{}
func (*BadBuilder) Build() *Pizza { return &Pizza{} }
func (*BadBuilder) ModifyPizza(p *Pizza) { p.Size = "large" }
// ✅ Better: Builder creates new object each timetype GoodBuilder struct{}
func (*GoodBuilder) Build() *Pizza { return &Pizza{} // New object each time}// Mistake 2: Builder Modifying Built Objectstruct Pizza { size: String, cheese: bool, pepperoni: bool,}impl Pizza { fn new(size: String, cheese: bool, pepperoni: bool) -> Self { Self { size, cheese, pepperoni, } }}Mistake 3: No Validation
Section titled “Mistake 3: No Validation”# ❌ Builder without validationclass BadBuilder: def build(self) -> Pizza: return self.pizza # No validation!
# ✅ Better: Validate before buildingclass GoodBuilder: def build(self) -> Pizza: if not self.pizza.size: raise ValueError("Size is required") if not self.pizza.crust: raise ValueError("Crust is required") return self.pizza// ❌ Builder without validationpublic class BadBuilder { public Pizza build() { return pizza; // No validation! }}
// ✅ Better: Validate before buildingpublic class GoodBuilder { public Pizza build() { if (pizza.getSize() == null) { throw new IllegalStateException("Size is required"); } if (pizza.getCrust() == null) { throw new IllegalStateException("Crust is required"); } return pizza; }}// ❌ Builder without validationclass BadBuilder { build(): Pizza { return this.pizza; // No validation! }}
// ✅ Better: Validate before buildingclass GoodBuilder { build(): Pizza { if (!this.pizza.size) { throw new Error("Size is required"); } if (!this.pizza.crust) { throw new Error("Crust is required"); } return this.pizza; }}#include <stdexcept>
// ❌ Builder without validationclass BadBuilder {public: Pizza build() { return pizza; // No validation! }};
// ✅ Better: Validate before buildingclass GoodBuilder {public: Pizza build() { if (!pizza.size.has_value()) { throw std::invalid_argument("Size is required"); } if (!pizza.crust.has_value()) { throw std::invalid_argument("Crust is required"); } return pizza; }};using System;
// ❌ Builder without validationpublic class BadBuilder{ public Pizza Build() { return pizza; // No validation! }}
// ✅ Better: Validate before buildingpublic class GoodBuilder{ public Pizza Build() { if (string.IsNullOrEmpty(pizza.Size)) { throw new InvalidOperationException("Size is required"); } if (string.IsNullOrEmpty(pizza.Crust)) { throw new InvalidOperationException("Crust is required"); } return pizza; }}// ❌ Builder without validationtype BadBuilder struct { pizza *Pizza}
func (b *BadBuilder) Build() *Pizza { return b.pizza }
// ✅ Better: Validate before buildingtype GoodBuilder struct { pizza *Pizza}
func (g *GoodBuilder) Build() *Pizza { if g.pizza.Size == "" { panic("Size is required") } if g.pizza.Crust == "" { panic("Crust is required") } return g.pizza}
type Pizza struct { Size, Crust string}// Mistake 3: No Validationstruct Pizza { size: String, cheese: bool, pepperoni: bool,}impl Pizza { fn new(size: String, cheese: bool, pepperoni: bool) -> Self { Self { size, cheese, pepperoni, } }}Benefits of Builder Pattern
Section titled “Benefits of Builder Pattern”- Readability - Code reads like English sentences
- Flexibility - Only set the options you need
- Validation - Can validate before object creation
- Immutability - Objects can be immutable after building
- Extensibility - Easy to add new options without breaking existing code
- Self-Documenting - Method names explain what they do
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Builder Pattern?
Section titled “What is Builder Pattern?”Builder Pattern is a creational design pattern that lets you construct complex objects step by step. Instead of passing many parameters to a constructor, you use a builder to set properties one at a time.
Why Use It?
Section titled “Why Use It?”- ✅ Readable - Code reads like English
- ✅ Flexible - Only set what you need
- ✅ Validated - Validate before building
- ✅ Self-documenting - Method names explain themselves
- ✅ Extensible - Easy to add new options
How It Works?
Section titled “How It Works?”- Create Product class - The object you want to build
- Create Builder class - Has methods to set each property
- Method chaining - Each method returns builder for chaining
- Build method - Validates and returns the final object
Key Components
Section titled “Key Components”Client → Builder.with_x().with_y().build() → Product- Product - The object being built
- Builder - Constructs the product step by step
- build() - Validates and returns the product
- Method chaining - Each method returns builder
Simple Example
Section titled “Simple Example”# Productclass Pizza: def __init__(self): self.size = None self.crust = None
# Builderclass PizzaBuilder: def __init__(self): self.pizza = Pizza() def with_size(self, size): self.pizza.size = size return self def build(self): return self.pizza
# Usagepizza = PizzaBuilder().with_size("large").with_crust("thin").build()class Pizza { String size, crust;}
class PizzaBuilder { private Pizza pizza = new Pizza(); PizzaBuilder withSize(String s) { pizza.size = s; return this; } Pizza build() { return pizza; }}
// UsagePizza pizza = new PizzaBuilder().withSize("large").build();class Pizza { size?: string; crust?: string;}
class PizzaBuilder { private pizza = new Pizza(); withSize(size: string): this { this.pizza.size = size; return this; } build(): Pizza { return this.pizza; }}
// Usageconst pizza = new PizzaBuilder().withSize("large").build();struct Pizza { std::string size, crust; };
class PizzaBuilder { Pizza pizza;public: PizzaBuilder& withSize(const std::string& s) { pizza.size = s; return *this; } Pizza build() { return pizza; }};
// UsagePizza pizza = PizzaBuilder().withSize("large").build();class Pizza { public string Size, Crust; }
class PizzaBuilder { private Pizza pizza = new(); public PizzaBuilder WithSize(string s) { pizza.Size = s; return this; } public Pizza Build() => pizza;}
// Usagevar pizza = new PizzaBuilder().WithSize("large").Build();// Producttype Pizza struct { Size, Crust string}
// Buildertype PizzaBuilder struct{ pizza Pizza }
func (b *PizzaBuilder) WithSize(size string) *PizzaBuilder { b.pizza.Size = size return b}func (b *PizzaBuilder) Build() Pizza { return b.pizza }
// Usage (example)func example() Pizza { return (&PizzaBuilder{}).WithSize("large").WithCrust("thin").Build()}// Simple Example#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}When to Use?
Section titled “When to Use?”✅ Many optional parameters
✅ Complex object construction
✅ Need validation before creation
✅ Want readable, self-documenting code
✅ Avoiding telescoping constructors
When NOT to Use?
Section titled “When NOT to Use?”❌ Simple objects with few parameters
❌ All parameters required
❌ Performance is critical
❌ Over-engineering simple cases
Key Takeaways
Section titled “Key Takeaways”- Builder Pattern = Construct objects step by step
- Builder = Step-by-step construction with validation
- Method chaining = Each method returns builder
- Benefit = Readable, flexible, validated construction
- Principle = Build complex objects with clarity
Common Pattern Structure
Section titled “Common Pattern Structure”# 1. Productclass Product: def __init__(self): self.field1 = None self.field2 = None
# 2. Builderclass Builder: def __init__(self): self.product = Product() def with_field1(self, value): self.product.field1 = value return self def build(self): return self.product
# 3. Usageproduct = Builder().with_field1("value").build()// 1. Productclass Product { String field1, field2; }
// 2. Builderclass Builder { private Product product = new Product(); Builder withField1(String v) { product.field1 = v; return this; } Product build() { return product; }}
// 3. UsageProduct product = new Builder().withField1("value").build();// 1. Productinterface Product { field1?: string; field2?: string; }
// 2. Builderclass Builder { private product: Product = {}; withField1(v: string): this { this.product.field1 = v; return this; } build(): Product { return this.product; }}
// 3. Usageconst product = new Builder().withField1("value").build();// 1. Productstruct Product { std::string field1, field2; };
// 2. Builderclass Builder { Product product;public: Builder& withField1(const std::string& v) { product.field1 = v; return *this; } Product build() { return product; }};
// 3. UsageProduct product = Builder().withField1("value").build();// 1. Productclass Product { public string Field1, Field2; }
// 2. Builderclass Builder { private Product product = new(); public Builder WithField1(string v) { product.Field1 = v; return this; } public Product Build() => product;}
// 3. Usagevar product = new Builder().WithField1("value").Build();// 1. Producttype Product struct{ Field1, Field2 string }
// 2. Buildertype Builder struct{ product Product }
func (b *Builder) WithField1(v string) *Builder { b.product.Field1 = v return b}func (b *Builder) Build() Product { return b.product }
// 3. Usagefunc exampleProduct() Product { return (&Builder{}).WithField1("value").Build()}// Common Pattern Structure#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}Remember
Section titled “Remember”- Builder Pattern simplifies complex object construction
- It makes code readable and self-documenting
- It allows validation before object creation
- Use it when construction is complex or you have many optional parameters
- Don’t use it for simple cases - avoid over-engineering!
Interview Focus: Builder Pattern
Section titled “Interview Focus: Builder Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Builder Pattern is a creational design pattern that lets you construct complex objects step by step. Instead of passing many parameters to a constructor, you use a builder with method chaining to set properties one at a time.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of creational patterns category
- Indicates you can explain concepts clearly
2. When to Use Builder Pattern
Section titled “2. When to Use Builder Pattern”Must mention:
- ✅ Many optional parameters - 5+ optional parameters
- ✅ Complex construction - Objects need step-by-step setup
- ✅ Readability - Want self-documenting code
- ✅ Validation - Need to validate before creation
- ✅ Avoiding telescoping constructors - Multiple constructors are messy
Example scenario to give:
“I’d use Builder Pattern when building HTTP requests or database query objects. These objects have many optional parameters like headers, query params, timeout, retries, etc. With Builder Pattern, the code reads like English and you only set what you need.”
3. Structure and Components
Section titled “3. Structure and Components”Must explain:
- Product - The object being built
- Builder - Constructs the product step by step
- Method chaining - Each method returns builder
- build() - Validates and returns the product
Visual explanation:
Client → Builder.with_x().with_y().build() → Product4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”Benefits to mention:
- Readability - Code reads like English sentences
- Flexibility - Only set what you need
- Validation - Can validate before creation
- Extensibility - Easy to add new options
- Self-documenting - Method names explain themselves
Trade-offs to acknowledge:
- Complexity - Adds builder class (may be overkill for simple cases)
- Performance - Small overhead (usually negligible)
- Over-engineering risk - Don’t use for simple objects
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “What’s the difference between Builder Pattern and Factory Pattern?”
A:
“Factory Pattern creates objects of different types based on input (like creating StripePayment vs PayPalPayment). Builder Pattern constructs a single complex object step by step (like building a Pizza with size, crust, toppings). Factory chooses what to create, Builder constructs how to create.”
Q: “When would you NOT use Builder Pattern?”
A:
“I wouldn’t use Builder Pattern for simple objects with 1-3 parameters - a constructor is simpler. Also, if all parameters are required, a constructor is fine. I’d avoid it if performance is critical, though usually the overhead is negligible.”
Q: “How does Builder Pattern relate to SOLID principles?”
A:
“Builder Pattern supports Single Responsibility Principle by separating construction logic from the product class. It supports Open/Closed Principle - you can add new builder methods without modifying existing code. It also helps with readability, making code easier to understand and maintain.”
6. Implementation Details
Section titled “6. Implementation Details”Key implementation points:
- Method chaining - Each method returns builder
def with_size(self, size: str) -> 'PizzaBuilder': self.pizza.size = size return self # Return self for chainingBuilder withSize(String size) { pizza.size = size; return this; // Return this for chaining}withSize(size: string): this { this.pizza.size = size; return this; // Return this for chaining}Builder& withSize(const std::string& size) { pizza.size = size; return *this; // Return *this for chaining}public Builder WithSize(string size) { pizza.Size = size; return this; // Return this for chaining}func (b *PizzaBuilder) WithSize(size string) *PizzaBuilder { b.pizza.Size = size return b // Return receiver for chaining}// 6. Implementation Details#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}- Validation in build() - Validate before returning
def build(self) -> Pizza: if not self.pizza.size: raise ValueError("Size is required") return self.pizzaPizza build() { if (pizza.size == null || pizza.size.isEmpty()) throw new IllegalStateException("Size is required"); return pizza;}build(): Pizza { if (!this.pizza.size) throw new Error("Size is required"); return this.pizza;}Pizza build() { if (pizza.size.empty()) throw std::invalid_argument("Size is required"); return pizza;}public Pizza Build() { if (string.IsNullOrEmpty(pizza.Size)) throw new InvalidOperationException("Size is required"); return pizza;}func (b *PizzaBuilder) Build() *Pizza { if b.pizza.Size == "" { panic("Size is required") } return b.pizza}// 6. Implementation Details#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}- Immutable after build - Product shouldn’t change after building
# Product should be immutable or builder shouldn't modify it after build// Make Product fields final; don't expose setters after build// Use readonly/const; freeze object or don't mutate after build// Use const for built product; don't expose mutation after build// Use init-only properties; don't expose setters after build// Prefer exported fields immutable after build by convention, or unexported fields with getters.// 6. Implementation Details#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}- Clear method names - Methods should be self-documenting
.with_size("large") # Clear what it does.add_topping("pepperoni") # Clear what it does.withSize("large") // Clear what it does.addTopping("pepperoni") // Clear what it does.withSize("large") // Clear what it does.addTopping("pepperoni") // Clear what it does.withSize("large") // Clear what it does.addTopping("pepperoni") // Clear what it does.WithSize("large") // Clear what it does.AddTopping("pepperoni") // Clear what it does.WithSize("large") // Clear what it does.AddTopping("pepperoni") // Clear what it does// 6. Implementation Details#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}7. Real-World Examples
Section titled “7. Real-World Examples”Good examples to mention:
- HTTP Requests - Headers, params, body, auth, timeout
- Database Queries - SELECT, WHERE, JOIN, ORDER BY clauses
- Email Messages - To, CC, BCC, subject, body, attachments
- Configuration Objects - Many optional settings
- UI Components - Many styling and behavior options
8. Common Mistakes to Avoid
Section titled “8. Common Mistakes to Avoid”Mistakes interviewers watch for:
-
Over-engineering - Using Builder for simple cases
- ❌ Bad: Builder for Point(x, y)
- ✅ Good: Builder for HttpRequest with 10+ options
-
No validation - Building invalid objects
- ❌ Bad: build() returns object without validation
- ✅ Good: build() validates before returning
-
Modifying built objects - Builder modifying already-built objects
- ❌ Bad: Builder reusing same object instance
- ✅ Good: Builder creates new object each time
-
Inconsistent method names - Methods don’t follow naming convention
- ❌ Bad: setSize(), addTopping(), configureCrust()
- ✅ Good: with_size(), add_topping(), with_crust()
9. Comparison with Other Patterns
Section titled “9. Comparison with Other Patterns”Builder vs Factory:
Builder vs Constructor:
- Builder - Step-by-step construction with validation
- Constructor - Direct instantiation with parameters
Builder vs Prototype:
10. Code Quality Points
Section titled “10. Code Quality Points”What interviewers look for:
✅ Clean code - Readable, well-structured
✅ Type hints - Proper type annotations
✅ Error handling - Validates input, raises exceptions
✅ Documentation - Clear docstrings
✅ SOLID principles - Follows design principles
✅ Testability - Easy to test and mock
Example of good code:
from typing import Optional
class HttpRequest: """HTTP request product""" def __init__(self): self.method: Optional[str] = None self.url: Optional[str] = None self.headers: dict = {}
class HttpRequestBuilder: """Builder for constructing HTTP requests"""
def __init__(self): self.request = HttpRequest()
def with_method(self, method: str) -> 'HttpRequestBuilder': """ Set HTTP method.
Args: method: HTTP method (GET, POST, etc.)
Returns: Builder instance for method chaining """ self.request.method = method.upper() return self
def build(self) -> HttpRequest: """ Build and return the HTTP request.
Returns: HttpRequest instance
Raises: ValueError: If required fields are missing """ if not self.request.method: raise ValueError("HTTP method is required") if not self.request.url: raise ValueError("URL is required") return self.requestpublic class HttpRequest { /** * HTTP request product */ private String method; private String url; private Map<String, String> headers = new HashMap<>();
// Getters public String getMethod() { return method; } public String getUrl() { return url; } public Map<String, String> getHeaders() { return headers; }
// Setters (package-private) void setMethod(String method) { this.method = method; } void setUrl(String url) { this.url = url; } void addHeader(String key, String value) { this.headers.put(key, value); }}
public class HttpRequestBuilder { /** * Builder for constructing HTTP requests */ private HttpRequest request = new HttpRequest();
/** * Set HTTP method. * * @param method HTTP method (GET, POST, etc.) * @return Builder instance for method chaining */ public HttpRequestBuilder withMethod(String method) { request.setMethod(method.toUpperCase()); return this; }
/** * Build and return the HTTP request. * * @return HttpRequest instance * @throws IllegalStateException If required fields are missing */ public HttpRequest build() { if (request.getMethod() == null) { throw new IllegalStateException("HTTP method is required"); } if (request.getUrl() == null) { throw new IllegalStateException("URL is required"); } return request; }}/** * HTTP request product */class HttpRequest { method?: string; url?: string; headers: Record<string, string> = {};}
/** * Builder for constructing HTTP requests */class HttpRequestBuilder { private request: HttpRequest;
constructor() { this.request = new HttpRequest(); }
/** * Set HTTP method. * * @param method - HTTP method (GET, POST, etc.) * @returns Builder instance for method chaining */ withMethod(method: string): HttpRequestBuilder { this.request.method = method.toUpperCase(); return this; }
/** * Set request URL. * * @param url - Request URL * @returns Builder instance for method chaining */ withUrl(url: string): HttpRequestBuilder { this.request.url = url; return this; }
/** * Build and return the HTTP request. * * @returns HttpRequest instance * @throws Error if required fields are missing */ build(): HttpRequest { if (!this.request.method) { throw new Error("HTTP method is required"); } if (!this.request.url) { throw new Error("URL is required"); } return this.request; }}#include <string>#include <map>#include <optional>#include <stdexcept>#include <algorithm>
/** * HTTP request product */class HttpRequest {public: std::optional<std::string> method; std::optional<std::string> url; std::map<std::string, std::string> headers;
// Getters std::optional<std::string> getMethod() const { return method; } std::optional<std::string> getUrl() const { return url; } const std::map<std::string, std::string>& getHeaders() const { return headers; }};
/** * Builder for constructing HTTP requests */class HttpRequestBuilder {private: HttpRequest request;
public: /** * Set HTTP method. * * @param method HTTP method (GET, POST, etc.) * @return Builder instance for method chaining */ HttpRequestBuilder& withMethod(const std::string& method) { std::string upperMethod = method; std::transform(upperMethod.begin(), upperMethod.end(), upperMethod.begin(), ::toupper); request.method = upperMethod; return *this; }
/** * Set request URL. * * @param url Request URL * @return Builder instance for method chaining */ HttpRequestBuilder& withUrl(const std::string& url) { request.url = url; return *this; }
/** * Build and return the HTTP request. * * @return HttpRequest instance * @throws std::invalid_argument if required fields are missing */ HttpRequest build() { if (!request.method.has_value()) { throw std::invalid_argument("HTTP method is required"); } if (!request.url.has_value()) { throw std::invalid_argument("URL is required"); } return request; }};using System;using System.Collections.Generic;
/// <summary>/// HTTP request product/// </summary>public class HttpRequest{ public string Method { get; set; } public string Url { get; set; } public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();}
/// <summary>/// Builder for constructing HTTP requests/// </summary>public class HttpRequestBuilder{ private HttpRequest request;
public HttpRequestBuilder() { request = new HttpRequest(); }
/// <summary> /// Set HTTP method. /// </summary> /// <param name="method">HTTP method (GET, POST, etc.)</param> /// <returns>Builder instance for method chaining</returns> public HttpRequestBuilder WithMethod(string method) { request.Method = method.ToUpper(); return this; }
/// <summary> /// Set request URL. /// </summary> /// <param name="url">Request URL</param> /// <returns>Builder instance for method chaining</returns> public HttpRequestBuilder WithUrl(string url) { request.Url = url; return this; }
/// <summary> /// Build and return the HTTP request. /// </summary> /// <returns>HttpRequest instance</returns> /// <exception cref="InvalidOperationException">Thrown when required fields are missing</exception> public HttpRequest Build() { if (string.IsNullOrEmpty(request.Method)) { throw new InvalidOperationException("HTTP method is required"); } if (string.IsNullOrEmpty(request.Url)) { throw new InvalidOperationException("URL is required"); } return request; }}package demo
import "strings"
/// HTTP request producttype HttpRequest struct { Method, URL string Headers map[string]string}
/// Builder for constructing HTTP requeststype HttpRequestBuilder struct { request *HttpRequest}
func NewInterviewHttpBuilder() *HttpRequestBuilder { return &HttpRequestBuilder{request: &HttpRequest{Headers: map[string]string{}}}}
/// Set HTTP method.func (b *HttpRequestBuilder) WithMethod(method string) *HttpRequestBuilder { b.request.Method = strings.ToUpper(method) return b}
/// Set request URL.func (b *HttpRequestBuilder) WithURL(url string) *HttpRequestBuilder { b.request.URL = url return b}
/// Build and return the HTTP request.func (b *HttpRequestBuilder) Build() (*HttpRequest, error) { if b.request.Method == "" { return nil, errHTTP("HTTP method is required") } if b.request.URL == "" { return nil, errHTTP("URL is required") } return b.request, nil}
type httpErr struct{ msg string }
func (e httpErr) Error() string { return e.msg }
func errHTTP(msg string) error { return httpErr{msg}}// 10. Code Quality Points#[derive(Default)]struct PizzaBuilder { size: String, cheese: bool, pepperoni: bool,}impl PizzaBuilder { fn size(mut self, size: &str) -> Self { self.size = size.into(); self } fn cheese(mut self) -> Self { self.cheese = true; self } fn build(self) -> Pizza { Pizza { size: self.size, cheese: self.cheese, pepperoni: self.pepperoni, } }}struct Pizza { size: String, cheese: bool, pepperoni: bool,}Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Builder Pattern clearly in one sentence
- Explain when to use it (with examples)
- Describe the structure and components
- List benefits and trade-offs
- Compare with other creational patterns
- Implement Builder Pattern from scratch
- Connect to SOLID principles
- Identify when NOT to use it
- Give 2-3 real-world examples
- Discuss common mistakes and how to avoid them
Remember: Builder Pattern is about constructing complex objects step by step with clarity and flexibility! 🏗️