Builder Pattern
Builder Pattern
Section titled โBuilder PatternโThe Builder pattern separates the construction of a complex object from its representation. Itโs especially useful when an object has many optional parameters or requires a specific construction sequence.
The Problem It Solves
Section titled โThe Problem It SolvesโA class with many optional parameters leads to:
// Telescoping constructor anti-patternvar email = new Email("to@example.com", "From Name", "Subject", "Body", null, null, "reply@example.com", true, false, Priority.High, null);// What does that 8th boolean mean?Builder Pattern in C#
Section titled โBuilder Pattern in C#โpublic class Email{ public string To { get; } public string Subject { get; } public string Body { get; } public string? From { get; } public string? ReplyTo { get; } public bool IsHtml { get; } public Priority Priority { get; } public IReadOnlyList<string> Cc { get; }
private Email(Builder builder) { To = builder.To; Subject = builder.Subject; Body = builder.Body; From = builder.From; ReplyTo = builder.ReplyTo; IsHtml = builder.IsHtml; Priority = builder.Priority; Cc = builder.Cc.AsReadOnly(); }
public class Builder { public string To { get; } public string Subject { get; } public string Body { get; } public string? From { get; private set; } public string? ReplyTo { get; private set; } public bool IsHtml { get; private set; } public Priority Priority { get; private set; } = Priority.Normal; public List<string> Cc { get; } = new();
public Builder(string to, string subject, string body) { To = to; Subject = subject; Body = body; }
public Builder WithFrom(string from) { From = from; return this; } public Builder WithReplyTo(string replyTo) { ReplyTo = replyTo; return this; } public Builder AsHtml() { IsHtml = true; return this; } public Builder WithPriority(Priority p) { Priority = p; return this; } public Builder WithCc(string address) { Cc.Add(address); return this; }
public Email Build() => new(this); }}
// Usage โ only set what you needvar email = new Email.Builder("alice@example.com", "Order Confirmed", body) .AsHtml() .WithPriority(Priority.High) .WithReplyTo("support@example.com") .Build();Fluent Builder in TypeScript
Section titled โFluent Builder in TypeScriptโclass QueryBuilder { private table = ''; private conditions: string[] = []; private columns: string[] = ['*']; private orderByClause = ''; private limitValue?: number;
from(table: string): this { this.table = table; return this; }
select(...columns: string[]): this { this.columns = columns; return this; }
where(condition: string): this { this.conditions.push(condition); return this; }
orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this { this.orderByClause = `ORDER BY ${column} ${direction}`; return this; }
limit(n: number): this { this.limitValue = n; return this; }
build(): string { if (!this.table) throw new Error('Table is required');
let sql = `SELECT ${this.columns.join(', ')} FROM ${this.table}`; if (this.conditions.length > 0) { sql += ` WHERE ${this.conditions.join(' AND ')}`; } if (this.orderByClause) sql += ` ${this.orderByClause}`; if (this.limitValue) sql += ` LIMIT ${this.limitValue}`;
return sql; }}
// Readable and flexibleconst query = new QueryBuilder() .from('users') .select('id', 'name', 'email') .where('active = true') .where('created_at > NOW() - INTERVAL 30 DAYS') .orderBy('created_at', 'DESC') .limit(20) .build();Builder with Validation
Section titled โBuilder with Validationโclass UserBuilder { private name = ''; private email = ''; private age?: number;
withName(name: string): this { this.name = name; return this; } withEmail(email: string): this { this.email = email; return this; } withAge(age: number): this { this.age = age; return this; }
build(): User { if (!this.name) throw new Error('Name is required'); if (!this.email) throw new Error('Email is required'); if (!this.email.includes('@')) throw new Error('Email is invalid'); if (this.age !== undefined && this.age < 0) throw new Error('Age must be positive');
return new User(this.name, this.email, this.age); }}Builder for Tests
Section titled โBuilder for TestsโBuilders are especially valuable in test setup โ use the โTest Data Builderโ or โObject Motherโ pattern:
class UserBuilder { private data = { id: '1', name: 'Test User', email: 'test@example.com', role: 'user' as UserRole, createdAt: new Date(), };
withId(id: string) { this.data.id = id; return this; } withName(name: string) { this.data.name = name; return this; } withEmail(email: string) { this.data.email = email; return this; } asAdmin() { this.data.role = 'admin'; return this; }
build(): User { return new User(this.data); }}
// Clean, readable test setupconst admin = new UserBuilder().withName('Alice').asAdmin().build();const user = new UserBuilder().withEmail('bob@example.com').build();When to Use
Section titled โWhen to Useโ- Object has more than 3-4 optional parameters
- Object construction requires multiple steps or validation
- You want readable, self-documenting object creation
- Creating test fixtures