Files
freemo d9e5668cec fix(skills): comprehensive final audit pass for programming-patterns skill
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
2026-04-15 13:22:13 -04:00

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 ---");
  • 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.