d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
8.7 KiB
8.7 KiB
Builder
Problem
You need to construct a complex object with many optional parameters. Telescoping constructors
(new Query(table, cols, where, order, limit, offset, joins...)) are unreadable. Passing a
config dict loses type safety and discoverability.
Solution
Separate construction from representation using a Builder with a fluent interface.
Each method sets one aspect and returns self/this, enabling method chaining.
A final build() method produces the immutable result.
QueryBuilder
.select("name", "email")
.from("users")
.where("active = true")
.orderBy("name")
.limit(10)
.build()
--> "SELECT name, email FROM users WHERE active = true ORDER BY name LIMIT 10"
When to Use
- Object construction has many optional or conditional steps.
- You want a readable, self-documenting creation API.
- The constructed object should be immutable once built.
- You need to build different representations from the same construction process.
When to Avoid
- The object has only a few required fields — a simple constructor is clearer.
- The build steps have no meaningful order or optionality.
- You are in a language with named/keyword arguments that solve the readability problem (though Builder still helps with validation).
Pseudocode
class QueryBuilder:
fields table, columns, conditions, ordering, rowLimit
method select(*cols) -> self:
columns = cols
return self
method from(tbl) -> self:
table = tbl
return self
method where(cond) -> self:
conditions.append(cond)
return self
method orderBy(col) -> self:
ordering = col
return self
method limit(n) -> self:
rowLimit = n
return self
method build() -> string:
validate table is set
sql = "SELECT " + join(columns, ", ") + " FROM " + table
if conditions: sql += " WHERE " + join(conditions, " AND ")
if ordering: sql += " ORDER BY " + ordering
if rowLimit: sql += " LIMIT " + rowLimit
return sql
// Usage
query = QueryBuilder().select("name","email").from("users").where("active = true").limit(10).build()
Python
"""Builder — QueryBuilder with fluent interface."""
class QueryBuilder:
"""Builds SQL SELECT statements step-by-step."""
def __init__(self) -> None:
self._table: str | None = None
self._columns: list[str] = ["*"]
self._conditions: list[str] = []
self._ordering: str | None = None
self._row_limit: int | None = None
self._offset: int | None = None
# --- Fluent setters (each returns self) ---
def select(self, *columns: str) -> "QueryBuilder":
self._columns = list(columns)
return self
def from_table(self, table: str) -> "QueryBuilder":
self._table = table
return self
def where(self, condition: str) -> "QueryBuilder":
self._conditions.append(condition)
return self
def order_by(self, column: str) -> "QueryBuilder":
self._ordering = column
return self
def limit(self, n: int) -> "QueryBuilder":
self._row_limit = n
return self
def offset(self, n: int) -> "QueryBuilder":
self._offset = n
return self
# --- Terminal operation ---
def build(self) -> str:
if not self._table:
raise ValueError("FROM table is required")
parts = [f"SELECT {', '.join(self._columns)}", f"FROM {self._table}"]
if self._conditions:
parts.append(f"WHERE {' AND '.join(self._conditions)}")
if self._ordering:
parts.append(f"ORDER BY {self._ordering}")
if self._row_limit is not None:
parts.append(f"LIMIT {self._row_limit}")
if self._offset is not None:
parts.append(f"OFFSET {self._offset}")
return " ".join(parts)
if __name__ == "__main__":
q1 = (
QueryBuilder()
.select("name", "email")
.from_table("users")
.where("active = true")
.where("age > 18")
.order_by("name")
.limit(10)
.build()
)
print(f"Query 1: {q1}")
q2 = (
QueryBuilder()
.from_table("orders")
.where("status = 'pending'")
.order_by("created_at")
.limit(50)
.offset(100)
.build()
)
print(f"Query 2: {q2}")
print("--- Builder demo complete ---")
Go
// builder.go — QueryBuilder with fluent interface
package main
import (
"fmt"
"strings"
)
// QueryBuilder constructs SQL SELECT statements.
type QueryBuilder struct {
table string
columns []string
conditions []string
ordering string
rowLimit int
offset int
hasLimit bool
hasOffset bool
}
// NewQueryBuilder creates a builder with default SELECT *.
func NewQueryBuilder() *QueryBuilder {
return &QueryBuilder{columns: []string{"*"}}
}
func (qb *QueryBuilder) Select(cols ...string) *QueryBuilder {
qb.columns = cols
return qb
}
func (qb *QueryBuilder) From(table string) *QueryBuilder {
qb.table = table
return qb
}
func (qb *QueryBuilder) Where(cond string) *QueryBuilder {
qb.conditions = append(qb.conditions, cond)
return qb
}
func (qb *QueryBuilder) OrderBy(col string) *QueryBuilder {
qb.ordering = col
return qb
}
func (qb *QueryBuilder) Limit(n int) *QueryBuilder {
qb.rowLimit = n
qb.hasLimit = true
return qb
}
func (qb *QueryBuilder) Offset(n int) *QueryBuilder {
qb.offset = n
qb.hasOffset = true
return qb
}
// Build produces the final SQL string.
func (qb *QueryBuilder) Build() (string, error) {
if qb.table == "" {
return "", fmt.Errorf("FROM table is required")
}
parts := []string{
"SELECT " + strings.Join(qb.columns, ", "),
"FROM " + qb.table,
}
if len(qb.conditions) > 0 {
parts = append(parts, "WHERE "+strings.Join(qb.conditions, " AND "))
}
if qb.ordering != "" {
parts = append(parts, "ORDER BY "+qb.ordering)
}
if qb.hasLimit {
parts = append(parts, fmt.Sprintf("LIMIT %d", qb.rowLimit))
}
if qb.hasOffset {
parts = append(parts, fmt.Sprintf("OFFSET %d", qb.offset))
}
return strings.Join(parts, " "), nil
}
func main() {
q1, _ := NewQueryBuilder().
Select("name", "email").
From("users").
Where("active = true").
Where("age > 18").
OrderBy("name").
Limit(10).
Build()
fmt.Println("Query 1:", q1)
q2, _ := NewQueryBuilder().
From("orders").
Where("status = 'pending'").
OrderBy("created_at").
Limit(50).
Offset(100).
Build()
fmt.Println("Query 2:", q2)
fmt.Println("--- Builder demo complete ---")
}
JavaScript
// builder.js — QueryBuilder with fluent interface
class QueryBuilder {
constructor() {
this._table = null;
this._columns = ["*"];
this._conditions = [];
this._ordering = null;
this._rowLimit = null;
this._offset = null;
}
select(...columns) {
this._columns = columns;
return this;
}
from(table) {
this._table = table;
return this;
}
where(condition) {
this._conditions.push(condition);
return this;
}
orderBy(column) {
this._ordering = column;
return this;
}
limit(n) {
this._rowLimit = n;
return this;
}
offset(n) {
this._offset = n;
return this;
}
build() {
if (!this._table) throw new Error("FROM table is required");
const parts = [
`SELECT ${this._columns.join(", ")}`,
`FROM ${this._table}`,
];
if (this._conditions.length > 0) {
parts.push(`WHERE ${this._conditions.join(" AND ")}`);
}
if (this._ordering) {
parts.push(`ORDER BY ${this._ordering}`);
}
if (this._rowLimit !== null) {
parts.push(`LIMIT ${this._rowLimit}`);
}
if (this._offset !== null) {
parts.push(`OFFSET ${this._offset}`);
}
return parts.join(" ");
}
}
// --- Demo ---
const q1 = new QueryBuilder()
.select("name", "email")
.from("users")
.where("active = true")
.where("age > 18")
.orderBy("name")
.limit(10)
.build();
console.log("Query 1:", q1);
const q2 = new QueryBuilder()
.from("orders")
.where("status = 'pending'")
.orderBy("created_at")
.limit(50)
.offset(100)
.build();
console.log("Query 2:", q2);
console.log("--- Builder demo complete ---");
Related Patterns
- Factory Method — Builder constructs step-by-step; Factory Method creates in one call. Use Builder when the product has many optional parts.
- Prototype — Both create complex objects. Builder assembles parts; Prototype clones an existing instance.
- Composite — Builder is often used to build Composite structures (e.g., a document tree).
- Fluent Interface — Builders commonly implement a fluent interface (method chaining returning
self). - Immutability — The object produced by a Builder is typically immutable after
build()is called.