Builder Pattern – construirea pas cu pas a obiectelor complexe
Când creezi obiecte complexe (multe proprietăți, pași de inițializare sau configurații opționale), apelurile lungi către constructor (new Complex(a, b, c, d, e)) devin greu de citit și de întreținut. Builder Pattern separă procesul de creare a unui obiect complex de reprezentarea sa, permițând construcția pas cu pas și oferind un API fluent, ușor de folosit.
🔍 Ce rezolvă Builder Pattern
-
Constructori cu mulți parametri (sau parametri opționali) → cod greu de citit.
-
Necesitatea de a crea variante diferite ale aceluiași tip (ex: obiect cu setări implicite vs. obiect cu setări personalizate).
-
Inițializare în pași (ex: validări, setări dependente una de alta, pași suplimentari).
Builderul oferă un API fluent (metode care se pot „lega” una după alta), facilitând citirea și testarea codului.
⚙️ Exemplu practic în C# — construire fluentă a unei comenzi (Order)
Imaginăm o clasă Order complexă (multe opțiuni, address, items, discounts):
public class Order
{
public Guid Id { get; init; }
public string CustomerName { get; init; }
public string ShippingAddress { get; init; }
public List<OrderItem> Items { get; init; } = new();
public decimal Discount { get; init; }
public DateTime CreatedAt { get; init; }
}
public record OrderItem(string Sku, int Quantity, decimal Price);
Builderul:
public class OrderBuilder
{
private readonly Order _order = new()
{
Id = Guid.NewGuid(),
CreatedAt = DateTime.UtcNow
};
public OrderBuilder ForCustomer(string name)
{
_order = _order with { CustomerName = name };
return this;
}
public OrderBuilder ShipTo(string address)
{
_order = _order with { ShippingAddress = address };
return this;
}
public OrderBuilder AddItem(string sku, int qty, decimal price)
{
var items = new List<OrderItem>(_order.Items) { new(sku, qty, price) };
_order = _order with { Items = items };
return this;
}
public OrderBuilder WithDiscount(decimal discount)
{
_order = _order with { Discount = discount };
return this;
}
public Order Build()
{
// validări finale
if (string.IsNullOrWhiteSpace(_order.CustomerName))
throw new InvalidOperationException("Customer is required");
return _order;
}
}
Utilizare fluentă:
var order = new OrderBuilder()
.ForCustomer("Ion Popescu")
.ShipTo("Str. Exemplu 10, București")
.AddItem("SKU-123", 2, 49.9m)
.AddItem("SKU-456", 1, 19.99m)
.WithDiscount(5m)
.Build();
Codul este clar, fiecare pas e explicit, iar validările sunt centralizate în metoda Build().
✅ Avantaje
-
Claritate: cod ușor de citit (în special pentru obiecte cu mulți parametri).
-
Extensibilitate: ușor de adăugat opțiuni noi fără a complica constructorii.
-
Imutabilitate: combinate cu record/init poți avea obiecte imutabile construite ergonom.
-
Testabilitate: poți construi instanțe de test rapid, doar cu pașii necesari.
⚠️ Dezavantaje / când să eviți
-
Introduce clase în plus (boilerplate) — poate părea supradimensionat pentru obiecte simple.
-
Dacă nu ai multe opțiuni/varianta, new simplu poate fi mai concis.
-
Poate fi folosit greșit pentru logica ce ar trebui în servicii — builderul trebuie să se ocupe doar de creare/inițializare.
🔁 Builder vs Factory
-
Factory Method / Abstract Factory: se concentrează pe alegerea tipului de obiect (ce clasă concretă instanțiem).
-
Builder: se concentrează pe construirea internă a unui obiect complex, pas cu pas, independent de tipul concret.
Cele două pot fi combinate: o fabrică poate returna diferiți builderi în funcție de context.
🧠 Bună practică în .NET
-
Folosește record + with pentru obiecte imutabile și builder care clonează/setează proprietăți.
-
Păstrează validările complexe în Build() și nu în metodele individuale.
-
Expune o variantă simplă de creare (static OrderBuilder.CreateDefault()) pentru cazuri comune.
🚀 Concluzie
Builder Pattern este un instrument excelent când obiectele tale devin complexe și ai nevoie de un API expresiv pentru a le construi. În .NET, combinat cu record și pattern-uri moderne, duce la cod clar, testabil și ușor de extins. Folosește-l când constructorii încep să fie grei de citit sau când vrei să oferi opțiuni de configurare pas-cu-pas pentru consumatorii tăi.
