install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/gof-builder-pattern" ~/.claude/skills/intense-visions-harness-engineering-gof-builder-pattern-bf527c && rm -rf "$T"
manifest:
agents/skills/codex/gof-builder-pattern/SKILL.mdsource content
GOF Builder Pattern
Construct complex objects step-by-step using fluent builders and director classes.
When to Use
- An object requires many optional parameters and a telescoping constructor is unreadable
- You need to construct the same object through different representations (JSON, XML, plain object)
- You want immutable objects with a mutable construction phase
- Validation should happen at
time, not during individual settersbuild()
Instructions
Fluent builder with TypeScript (most common pattern):
interface QueryConfig { table: string; conditions: string[]; orderBy?: string; limit?: number; offset?: number; joins: string[]; } class QueryBuilder { private config: Partial<QueryConfig> = { conditions: [], joins: [] }; from(table: string): this { this.config.table = table; return this; } where(condition: string): this { this.config.conditions!.push(condition); return this; } join(clause: string): this { this.config.joins!.push(clause); return this; } orderBy(column: string): this { this.config.orderBy = column; return this; } limit(n: number): this { this.config.limit = n; return this; } offset(n: number): this { this.config.offset = n; return this; } build(): string { if (!this.config.table) throw new Error('Table is required'); let query = `SELECT * FROM ${this.config.table}`; if (this.config.joins!.length > 0) { query += ` ${this.config.joins!.join(' ')}`; } if (this.config.conditions!.length > 0) { query += ` WHERE ${this.config.conditions!.join(' AND ')}`; } if (this.config.orderBy) query += ` ORDER BY ${this.config.orderBy}`; if (this.config.limit !== undefined) query += ` LIMIT ${this.config.limit}`; if (this.config.offset !== undefined) query += ` OFFSET ${this.config.offset}`; return query; } } // Usage const query = new QueryBuilder() .from('users') .join('LEFT JOIN orders ON orders.user_id = users.id') .where('users.active = true') .where('users.age > 18') .orderBy('users.created_at DESC') .limit(20) .offset(40) .build();
Immutable result via build():
class EmailBuilder { private to: string[] = []; private subject = ''; private body = ''; private cc: string[] = []; private attachments: Buffer[] = []; addTo(address: string): this { this.to.push(address); return this; } withSubject(subject: string): this { this.subject = subject; return this; } withBody(body: string): this { this.body = body; return this; } addCC(address: string): this { this.cc.push(address); return this; } addAttachment(data: Buffer): this { this.attachments.push(data); return this; } build(): Readonly<Email> { if (this.to.length === 0) throw new Error('At least one recipient required'); if (!this.subject) throw new Error('Subject is required'); return Object.freeze({ to: [...this.to], subject: this.subject, body: this.body, cc: [...this.cc], attachments: [...this.attachments], }); } }
Director class (for reusable construction sequences):
class ReportDirector { constructor(private builder: ReportBuilder) {} buildSummaryReport(): void { this.builder .setTitle('Summary Report') .addSection('Overview') .addSection('KPIs') .setFooter('Confidential'); } buildDetailedReport(): void { this.builder .setTitle('Detailed Report') .addSection('Executive Summary') .addSection('Data Analysis') .addSection('Charts') .addSection('Raw Data') .addSection('Appendix') .setFooter('Confidential'); } }
Details
Why not just use an options object? Options objects work well for simple configs, but builders shine when:
- Validation logic is non-trivial and should fail loudly at
build() - The construction process has ordering constraints
- You need to produce multiple output formats from one construction process
Anti-patterns:
- Builder methods that mutate the built object after
— freeze the resultbuild() - Builders with required fields that can only be caught at runtime — consider marking with the type system or throwing at
build() - Directors that mix construction logic with business logic — keep Directors thin
TypeScript step builder (enforce required fields at compile time):
// Forces callers to provide required fields before optional ones interface NeedsTable { from(table: string): NeedsCondition; } interface NeedsCondition { where(cond: string): NeedsCondition; build(): string; limit(n: number): NeedsCondition; } class TypeSafeQueryBuilder implements NeedsTable, NeedsCondition { private table = ''; private conditions: string[] = []; private limitVal?: number; from(table: string): NeedsCondition { this.table = table; return this; } where(cond: string): NeedsCondition { this.conditions.push(cond); return this; } limit(n: number): NeedsCondition { this.limitVal = n; return this; } build(): string { return `SELECT * FROM ${this.table} WHERE ${this.conditions.join(' AND ')}`; } static create(): NeedsTable { return new TypeSafeQueryBuilder(); } } // Compile error if you skip from() const q = TypeSafeQueryBuilder.create().from('users').where('active = true').build();
Source
refactoring.guru/design-patterns/builder
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.