CodeOath
← All posts
Architecture & Patterns80 min total · 26 parts

ACID, SOLID, and Common Design Patterns: A Software Design Reference

Contents — Part 14 of 26: Creational Patterns: Builder and Prototype
Part 14 of 26 · ~2 min

Creational Patterns: Builder and Prototype

Builder constructs a complex object step-by-step, which matters most when that object has many optional parameters — the alternative being either a constructor with a dozen parameters (unreadable at the call site, and easy to pass arguments in the wrong order) or a dozen overloads (a combinatorial mess called the "telescoping constructor" problem).

public class Pizza
{
    public string Size { get; set; } = "medium";
    public bool ExtraCheese { get; set; }
    public bool Pepperoni { get; set; }
    public bool Mushrooms { get; set; }
}

public class PizzaBuilder
{
    private readonly Pizza _pizza = new();

    public PizzaBuilder WithSize(string size) { _pizza.Size = size; return this; }
    public PizzaBuilder AddExtraCheese() { _pizza.ExtraCheese = true; return this; }
    public PizzaBuilder AddPepperoni() { _pizza.Pepperoni = true; return this; }
    public PizzaBuilder AddMushrooms() { _pizza.Mushrooms = true; return this; }
    public Pizza Build() => _pizza;
}

// Readable at the call site, in any order, with only the options that matter named explicitly.
var pizza = new PizzaBuilder()
    .WithSize("large")
    .AddExtraCheese()
    .AddPepperoni()
    .Build();

Prototype creates a new object by cloning an existing one, rather than constructing it from scratch — useful when building an object from raw inputs is expensive (an expensive database lookup, a computed configuration) but a small variation of an already-built object is what's actually needed.

public class DocumentTemplate : ICloneable
{
    public string Header { get; set; } = "";
    public string Footer { get; set; } = "";
    public List<string> Sections { get; set; } = new();

    public object Clone()
    {
        // A shallow Clone() is not always enough — Sections is a reference type,
        // so both the original and the clone would share the same List instance
        // unless it's copied explicitly, as done here.
        return new DocumentTemplate
        {
            Header = Header,
            Footer = Footer,
            Sections = new List<string>(Sections)
        };
    }
}

var baseTemplate = new DocumentTemplate { Header = "Company Report", Footer = "Confidential" };
var invoiceVariant = (DocumentTemplate)baseTemplate.Clone();
invoiceVariant.Sections.Add("Invoice details"); // doesn't affect baseTemplate.Sections

That comment about shallow versus deep cloning is the real gotcha with Prototype in any language with reference types: a naive member-wise clone copies references to nested objects, not the objects themselves, so mutating a "cloned" collection can silently mutate the original too unless the clone logic explicitly copies nested reference-type fields.