Overview
The Builder Pattern addresses the problem of constructing objects with numerous optional parameters or complex initialization sequences. Rather than using telescoping constructors with many parameters or requiring clients to call multiple setter methods, the Builder Pattern provides a dedicated object that handles construction through a fluent, step-by-step interface.
The pattern emerged from the Gang of Four design patterns catalog as a solution to the inflexibility of constructors. When an object requires many configuration options, constructors become unwieldy. A constructor with ten parameters forces callers to understand parameter order and provide values for options they may not care about. The Builder Pattern extracts this construction logic into a separate class, allowing clients to specify only the configuration they need.
The pattern operates through two primary components: the Builder, which provides methods for setting individual aspects of the object being constructed, and optionally a Director, which encapsulates common construction sequences. The Builder accumulates configuration through method calls, then creates the final object when requested. This separation means the same construction process can create different representations of an object.
# Without Builder Pattern - telescoping constructor
report = Report.new(
title: "Sales Report",
format: :pdf,
include_charts: true,
include_summary: true,
page_size: :a4,
orientation: :landscape,
font_size: 12,
color_scheme: :corporate
)
# With Builder Pattern - selective configuration
report = ReportBuilder.new
.title("Sales Report")
.format(:pdf)
.include_charts
.landscape
.build
The pattern finds particular value in domain-specific languages (DSLs), configuration systems, test data generation, and any context where object construction involves complex setup or numerous optional components.
Key Principles
Separation of Construction and Representation
The fundamental principle separates how an object is built from what it represents. The Builder encapsulates construction logic while the product class represents the final object. This separation allows the same construction process to produce different types of objects, and different builders to create variations of the same product type.
class HTMLBuilder
def initialize
@elements = []
end
def add_heading(text)
@elements << "<h1>#{text}</h1>"
self
end
def add_paragraph(text)
@elements << "<p>#{text}</p>"
self
end
def build
@elements.join("\n")
end
end
class MarkdownBuilder
def initialize
@elements = []
end
def add_heading(text)
@elements << "# #{text}"
self
end
def add_paragraph(text)
@elements << text
self
end
def build
@elements.join("\n\n")
end
end
# Same construction process, different representations
def create_document(builder)
builder.add_heading("Welcome")
.add_paragraph("This is content")
.build
end
Step-by-Step Construction
Builders construct objects incrementally rather than requiring all parameters at once. Each method adds one aspect of the final object, accumulating state until the build method finalizes construction. This approach provides clarity about what gets configured and supports optional parameters naturally.
The incremental nature allows validation at each step rather than only at construction time. A builder can verify that added components are compatible or meet certain constraints before accepting them. This early validation prevents invalid object creation and provides better error messages.
Method Chaining and Fluent Interfaces
Builders typically return self from configuration methods, enabling method chaining. This creates a fluent interface where multiple configuration calls read like sentences. The pattern distinguishes between configuration methods (which return the builder) and the terminal build method (which returns the product).
class QueryBuilder
def initialize(table)
@table = table
@conditions = []
@order = nil
@limit = nil
end
def where(condition)
@conditions << condition
self # Enable chaining
end
def order_by(column)
@order = column
self # Enable chaining
end
def limit(count)
@limit = count
self # Enable chaining
end
def build
query = "SELECT * FROM #{@table}"
query += " WHERE #{@conditions.join(' AND ')}" unless @conditions.empty?
query += " ORDER BY #{@order}" if @order
query += " LIMIT #{@limit}" if @limit
query # Terminal method returns product
end
end
# Fluent usage
query = QueryBuilder.new("users")
.where("active = true")
.where("created_at > '2024-01-01'")
.order_by("last_name")
.limit(50)
.build
Immutability Options
Builders can construct either mutable or immutable products. For immutable products, the builder accumulates configuration in mutable state, then creates an immutable object in the build method. The builder itself may be mutable (accumulating state) or immutable (creating new builder instances at each step).
Immutable builders sacrifice performance for safety, ensuring that shared builder references cannot interfere with each other. Mutable builders offer better performance but require care when builders might be shared or reused.
Director Pattern
The optional Director component encapsulates common construction sequences. While builders provide the primitives for construction, directors combine these primitives into standard configurations. This separates the mechanics of building from common usage patterns.
class HTTPRequestBuilder
def initialize
@method = :get
@headers = {}
@body = nil
end
def method(http_method)
@method = http_method
self
end
def header(key, value)
@headers[key] = value
self
end
def body(content)
@body = content
self
end
def build
HTTPRequest.new(@method, @headers, @body)
end
end
class HTTPRequestDirector
def initialize(builder)
@builder = builder
end
def json_post(url, data)
@builder
.method(:post)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.body(JSON.generate(data))
.build
end
def authenticated_get(url, token)
@builder
.method(:get)
.header("Authorization", "Bearer #{token}")
.build
end
end
Design Considerations
When to Apply the Builder Pattern
The Builder Pattern provides value when object construction involves multiple steps or numerous parameters. Objects with more than four or five constructor parameters become candidates for builders, particularly when many parameters are optional. The pattern excels when construction logic is complex enough to obscure the object's purpose or when multiple representations of constructed objects exist.
Consider builders when telescoping constructors (multiple constructor overloads with different parameter combinations) emerge in the codebase. Telescoping constructors force callers to understand which constructor variant to use and become unmaintainable as parameter combinations grow. Builders eliminate this problem by making each configuration option explicit.
The pattern fits domain-specific languages where object creation reads like natural language. Test fixture builders, query builders, and document builders demonstrate this. Configuration objects that combine many optional settings benefit from builders, as do objects requiring validation logic distributed across multiple properties.
When to Avoid the Builder Pattern
Simple objects with few required parameters and no optional parameters gain little from builders. A user class with only name and email parameters needs no builder - a constructor suffices. The builder adds complexity without corresponding benefit.
Objects with interdependent parameters that must be validated together make poor candidates for step-by-step construction. If parameter A's validity depends on parameter B's value, validating at each step becomes complex. In such cases, a factory method or constructor with parameter validation may work better.
Performance-critical code where object creation happens in tight loops should avoid builders unless profiling demonstrates acceptable overhead. Each builder creates an additional object that must be garbage collected. For simple objects created millions of times per second, direct constructor calls prove more efficient.
Trade-offs Against Alternative Patterns
Builders trade simplicity for flexibility. A simple constructor requires less code and fewer classes but handles optional parameters poorly. Builders require an additional class and more initial development effort but provide superior clarity for complex construction.
Factory patterns and builders address different problems. Factories encapsulate object creation logic and select between different product types based on parameters. Builders configure a single product type through step-by-step construction. A factory might use a builder internally to construct the objects it creates.
Parameter objects bundle related parameters into a single object passed to a constructor. This approach works well for cohesive parameter groups but still requires callers to construct the parameter object. Builders can construct the parameter object itself or the final product.
Static factory methods provide named constructors that clarify intent. A User.from_oauth(oauth_data) factory method explains what it creates better than User.new(oauth_data). Builders provide similar benefits through method naming but support more complex construction sequences.
# Constructor with many parameters
class EmailMessage
def initialize(to, from, subject, body, cc = nil, bcc = nil,
reply_to = nil, attachments = [], priority = :normal,
content_type = "text/plain")
# ...
end
end
# Factory method
class EmailMessage
def self.simple_text(to, from, subject, body)
new(to, from, subject, body, nil, nil, nil, [], :normal, "text/plain")
end
end
# Builder approach
class EmailMessageBuilder
def to(address)
@to = address
self
end
def from(address)
@from = address
self
end
def subject(text)
@subject = text
self
end
def body(text)
@body = text
self
end
def cc(address)
@cc = address
self
end
def attach(file)
(@attachments ||= []) << file
self
end
def priority(level)
@priority = level
self
end
def build
EmailMessage.new(@to, @from, @subject, @body, @cc, @bcc,
@reply_to, @attachments || [], @priority || :normal,
@content_type || "text/plain")
end
end
Mutability Considerations
Builder mutability affects how builders get shared and reused. Mutable builders maintain state across method calls, making them unsuitable for concurrent use or sharing between construction processes. Once a mutable builder creates an object, resetting it for reuse requires explicit reset logic.
Immutable builders create new builder instances at each step, preventing interference between usages. This safety comes at the cost of creating more objects and potential performance overhead. Immutable builders work naturally with concurrent code since no shared mutable state exists.
The product's mutability relates to but differs from the builder's mutability. A mutable builder can construct immutable products by copying its accumulated state into an immutable object during build. An immutable builder can construct mutable products by passing its state to a mutable object constructor.
Ruby Implementation
Method Chaining with Self Return
Ruby builders rely heavily on returning self from configuration methods to enable chaining. This pattern integrates naturally with Ruby's syntax, creating readable configuration sequences. The final build method breaks the chain by returning the constructed product.
class ConfigurationBuilder
def initialize
@config = {}
end
def database(connection_string)
@config[:database] = connection_string
self
end
def cache(type, options = {})
@config[:cache] = { type: type, options: options }
self
end
def logging(level)
@config[:logging_level] = level
self
end
def build
Configuration.new(@config)
end
end
config = ConfigurationBuilder.new
.database("postgresql://localhost/myapp")
.cache(:redis, host: "localhost", port: 6379)
.logging(:info)
.build
Tap Method for Inline Configuration
Ruby's tap method provides an alternative to explicit method chaining by yielding the builder to a block. This approach groups related configurations and enables conditional logic during building without breaking the chain.
class ServerBuilder
attr_reader :host, :port, :ssl_enabled, :ssl_cert
def initialize
@host = "localhost"
@port = 8080
@ssl_enabled = false
end
def host(value)
@host = value
self
end
def port(value)
@port = value
self
end
def enable_ssl(cert_path)
@ssl_enabled = true
@ssl_cert = cert_path
self
end
def build
Server.new(@host, @port, @ssl_enabled, @ssl_cert)
end
end
# Using tap for grouped configuration
server = ServerBuilder.new.tap { |builder|
builder.host("api.example.com")
builder.port(443)
builder.enable_ssl("/path/to/cert.pem") if ENV['PRODUCTION']
}.build
Block-Based Builder DSL
Ruby blocks enable elegant builder DSLs where the builder yields itself to a block, allowing configuration without explicit variable references. This pattern creates highly readable construction code that looks like configuration files.
class RouteBuilder
def initialize
@routes = []
end
def get(path, &handler)
@routes << { method: :get, path: path, handler: handler }
self
end
def post(path, &handler)
@routes << { method: :post, path: path, handler: handler }
self
end
def namespace(prefix, &block)
nested_builder = RouteBuilder.new
nested_builder.instance_eval(&block)
@routes.concat(nested_builder.build.map { |r|
r.merge(path: "#{prefix}#{r[:path]}")
})
self
end
def build
@routes
end
def self.define(&block)
builder = new
builder.instance_eval(&block)
builder.build
end
end
# DSL usage
routes = RouteBuilder.define do
get "/users" do
# handler code
end
post "/users" do
# handler code
end
namespace "/api/v1" do
get "/posts" do
# handler code
end
end
end
Struct-Based Simple Builders
For simpler cases, Ruby's Struct provides a lightweight builder alternative. Defining a Struct with keyword arguments creates an object with builder-like initialization, though without method chaining.
# Struct as a simple builder
ReportConfig = Struct.new(
:title,
:format,
:include_charts,
:include_summary,
:page_size,
keyword_init: true
) do
def self.build(**attributes)
new(
title: attributes[:title] || "Untitled Report",
format: attributes[:format] || :pdf,
include_charts: attributes.fetch(:include_charts, true),
include_summary: attributes.fetch(:include_summary, true),
page_size: attributes[:page_size] || :a4
)
end
end
config = ReportConfig.build(
title: "Q4 Results",
format: :html,
include_charts: false
)
Keyword Arguments as Builder Alternative
Ruby's keyword arguments provide native support for optional parameters with defaults, reducing the need for builders in simpler cases. This approach works well when construction requires no complex logic and all parameters are independent.
class Report
def initialize(title:, format: :pdf, include_charts: true,
include_summary: true, page_size: :a4)
@title = title
@format = format
@include_charts = include_charts
@include_summary = include_summary
@page_size = page_size
end
end
# Clear initialization without builder
report = Report.new(
title: "Sales Report",
format: :html,
include_charts: false
)
Validation in Ruby Builders
Ruby builders can perform validation at each configuration step or defer validation until build time. Immediate validation provides faster feedback but complicates builder methods. Deferred validation simplifies configuration methods but delays error detection.
class DatabaseConnectionBuilder
def initialize
@config = {}
end
def host(value)
raise ArgumentError, "Host cannot be empty" if value.to_s.strip.empty?
@config[:host] = value
self
end
def port(value)
raise ArgumentError, "Port must be between 1 and 65535" unless (1..65535).cover?(value)
@config[:port] = value
self
end
def database(value)
@config[:database] = value
self
end
def build
# Deferred validation of required fields
raise "Host is required" unless @config[:host]
raise "Database is required" unless @config[:database]
# Set defaults
@config[:port] ||= 5432
DatabaseConnection.new(@config)
end
end
Common Patterns
Classic Builder Pattern
The classic Gang of Four builder separates the builder interface from concrete implementations. An abstract builder defines the construction interface, and concrete builders implement specific variations. A director orchestrates the construction process.
class DocumentBuilder
def add_title(text); raise NotImplementedError; end
def add_section(heading, content); raise NotImplementedError; end
def add_image(path); raise NotImplementedError; end
def get_result; raise NotImplementedError; end
end
class PDFDocumentBuilder < DocumentBuilder
def initialize
@document = PDFDocument.new
end
def add_title(text)
@document.add_element(PDFTitle.new(text, size: 24))
end
def add_section(heading, content)
@document.add_element(PDFHeading.new(heading, level: 2))
@document.add_element(PDFText.new(content))
end
def add_image(path)
@document.add_element(PDFImage.new(path))
end
def get_result
@document
end
end
class HTMLDocumentBuilder < DocumentBuilder
def initialize
@html = []
end
def add_title(text)
@html << "<h1>#{text}</h1>"
end
def add_section(heading, content)
@html << "<h2>#{heading}</h2>"
@html << "<p>#{content}</p>"
end
def add_image(path)
@html << "<img src='#{path}' />"
end
def get_result
@html.join("\n")
end
end
class DocumentDirector
def initialize(builder)
@builder = builder
end
def construct_report(title, sections, images)
@builder.add_title(title)
sections.each { |heading, content| @builder.add_section(heading, content) }
images.each { |path| @builder.add_image(path) }
@builder.get_result
end
end
# Usage
pdf_builder = PDFDocumentBuilder.new
director = DocumentDirector.new(pdf_builder)
pdf = director.construct_report(
"Annual Report",
[["Overview", "Company overview..."], ["Financials", "Financial data..."]],
["chart1.png", "chart2.png"]
)
Fluent Builder Pattern
Fluent builders emphasize readability through method chaining and natural language method names. This pattern prioritizes the client-side code's appearance over implementation simplicity.
class EmailBuilder
def initialize
@to = []
@cc = []
@attachments = []
end
def to(address)
@to << address
self
end
def cc(address)
@cc << address
self
end
def from(address)
@from = address
self
end
def subject(text)
@subject = text
self
end
def body(text)
@body = text
self
end
def attach(filename)
@attachments << filename
self
end
def with_priority(level)
@priority = level
self
end
def build
Email.new(
to: @to,
cc: @cc,
from: @from,
subject: @subject,
body: @body,
attachments: @attachments,
priority: @priority
)
end
end
# Highly readable construction
email = EmailBuilder.new
.from("sender@example.com")
.to("recipient@example.com")
.cc("manager@example.com")
.subject("Project Update")
.body("Here is the latest status...")
.attach("report.pdf")
.attach("slides.pptx")
.with_priority(:high)
.build
Step Builder Pattern
Step builders enforce construction order by returning different builder types at each step. This pattern ensures clients cannot omit required configuration or configure steps in the wrong order. Each step's builder provides only the methods valid for that step.
class PaymentBuilder
def self.for_amount(amount)
AmountStep.new(amount)
end
class AmountStep
def initialize(amount)
@amount = amount
end
def from_account(account)
AccountStep.new(@amount, account)
end
end
class AccountStep
def initialize(amount, account)
@amount = amount
@account = account
end
def to_recipient(recipient)
RecipientStep.new(@amount, @account, recipient)
end
end
class RecipientStep
def initialize(amount, account, recipient)
@amount = amount
@account = account
@recipient = recipient
end
def with_reference(reference)
@reference = reference
self
end
def execute
Payment.new(
amount: @amount,
account: @account,
recipient: @recipient,
reference: @reference
)
end
end
end
# Enforced step order
payment = PaymentBuilder
.for_amount(1000.00)
.from_account("123-456-789")
.to_recipient("recipient@example.com")
.with_reference("Invoice #1234")
.execute
Test Data Builder Pattern
Test data builders create objects for testing with sensible defaults and selective overrides. This pattern reduces test setup code and improves test readability by making only relevant attributes explicit.
class UserBuilder
def initialize
@attributes = {
name: "Test User",
email: "test@example.com",
role: :user,
active: true,
created_at: Time.now
}
end
def with_name(name)
@attributes[:name] = name
self
end
def with_email(email)
@attributes[:email] = email
self
end
def as_admin
@attributes[:role] = :admin
self
end
def inactive
@attributes[:active] = false
self
end
def created_at(time)
@attributes[:created_at] = time
self
end
def build
User.new(@attributes)
end
def create
User.create(@attributes)
end
end
# Test usage - only specify what matters
describe "User permissions" do
it "allows admins to delete posts" do
admin = UserBuilder.new.as_admin.build
expect(admin.can_delete_posts?).to be true
end
it "prevents inactive users from logging in" do
user = UserBuilder.new.inactive.build
expect(user.can_login?).to be false
end
end
Practical Examples
HTTP Request Builder
Building HTTP requests involves numerous optional headers, authentication schemes, body formats, and timeout settings. A builder provides a clean interface for constructing requests without exposing HTTP library details.
class HTTPRequestBuilder
def initialize(url)
@url = url
@method = :get
@headers = {}
@query_params = {}
@body = nil
@timeout = 30
@follow_redirects = true
end
def method(http_method)
@method = http_method
self
end
def header(key, value)
@headers[key] = value
self
end
def headers(hash)
@headers.merge!(hash)
self
end
def query(key, value)
@query_params[key] = value
self
end
def json_body(data)
@body = JSON.generate(data)
@headers["Content-Type"] = "application/json"
self
end
def form_body(data)
@body = URI.encode_www_form(data)
@headers["Content-Type"] = "application/x-www-form-urlencoded"
self
end
def basic_auth(username, password)
credentials = Base64.strict_encode64("#{username}:#{password}")
@headers["Authorization"] = "Basic #{credentials}"
self
end
def bearer_token(token)
@headers["Authorization"] = "Bearer #{token}"
self
end
def timeout(seconds)
@timeout = seconds
self
end
def no_redirects
@follow_redirects = false
self
end
def build
HTTPRequest.new(
url: @url,
method: @method,
headers: @headers,
query_params: @query_params,
body: @body,
timeout: @timeout,
follow_redirects: @follow_redirects
)
end
end
# Building various request types
get_request = HTTPRequestBuilder.new("https://api.example.com/users")
.query("page", 1)
.query("per_page", 50)
.bearer_token("abc123")
.timeout(10)
.build
post_request = HTTPRequestBuilder.new("https://api.example.com/users")
.method(:post)
.json_body({ name: "John Doe", email: "john@example.com" })
.bearer_token("abc123")
.build
SQL Query Builder
Query builders abstract SQL construction, providing type safety and preventing SQL injection. The builder validates query components and generates properly escaped SQL.
class SQLQueryBuilder
def initialize(table)
@table = table
@select_columns = ["*"]
@joins = []
@conditions = []
@group_by = []
@having = []
@order_by = []
@limit_value = nil
@offset_value = nil
end
def select(*columns)
@select_columns = columns
self
end
def join(table, on_clause)
@joins << "JOIN #{table} ON #{on_clause}"
self
end
def left_join(table, on_clause)
@joins << "LEFT JOIN #{table} ON #{on_clause}"
self
end
def where(condition, *params)
@conditions << [condition, params]
self
end
def group_by(*columns)
@group_by.concat(columns)
self
end
def having(condition)
@having << condition
self
end
def order_by(column, direction = :asc)
@order_by << "#{column} #{direction.to_s.upcase}"
self
end
def limit(count)
@limit_value = count
self
end
def offset(count)
@offset_value = count
self
end
def build
query = "SELECT #{@select_columns.join(', ')} FROM #{@table}"
query += " #{@joins.join(' ')}" unless @joins.empty?
unless @conditions.empty?
where_clauses = @conditions.map { |cond, _| cond }
query += " WHERE #{where_clauses.join(' AND ')}"
end
query += " GROUP BY #{@group_by.join(', ')}" unless @group_by.empty?
query += " HAVING #{@having.join(' AND ')}" unless @having.empty?
query += " ORDER BY #{@order_by.join(', ')}" unless @order_by.empty?
query += " LIMIT #{@limit_value}" if @limit_value
query += " OFFSET #{@offset_value}" if @offset_value
# Return query and collected parameters
params = @conditions.flat_map { |_, p| p }
[query, params]
end
end
# Complex query construction
query, params = SQLQueryBuilder.new("orders")
.select("orders.id", "orders.total", "users.name")
.join("users", "users.id = orders.user_id")
.where("orders.status = ?", "completed")
.where("orders.created_at > ?", Date.today - 30)
.group_by("users.id")
.having("SUM(orders.total) > 1000")
.order_by("orders.created_at", :desc)
.limit(20)
.build
Configuration Builder
Application configuration involves many optional settings with sensible defaults. A builder provides a clear API for configuration while handling validation and default values.
class AppConfigBuilder
def initialize
@config = {
database: {},
cache: {},
logging: {},
features: {}
}
end
def database_url(url)
@config[:database][:url] = url
self
end
def database_pool_size(size)
@config[:database][:pool_size] = size
self
end
def redis_cache(host: "localhost", port: 6379, db: 0)
@config[:cache] = {
type: :redis,
host: host,
port: port,
db: db
}
self
end
def memory_cache(max_size: 1000)
@config[:cache] = {
type: :memory,
max_size: max_size
}
self
end
def log_level(level)
@config[:logging][:level] = level
self
end
def log_to_file(path)
@config[:logging][:file] = path
self
end
def enable_feature(name)
@config[:features][name] = true
self
end
def disable_feature(name)
@config[:features][name] = false
self
end
def build
# Apply defaults
@config[:database][:pool_size] ||= 5
@config[:logging][:level] ||= :info
@config[:cache][:type] ||= :memory
# Validate
raise "Database URL is required" unless @config[:database][:url]
AppConfig.new(@config)
end
end
# Environment-specific configuration
config = AppConfigBuilder.new
.database_url(ENV['DATABASE_URL'])
.database_pool_size(20)
.redis_cache(host: ENV['REDIS_HOST'], port: 6379)
.log_level(:debug)
.log_to_file("log/app.log")
.enable_feature(:new_dashboard)
.enable_feature(:api_v2)
.build
Reference
Builder Method Classification
| Method Type | Purpose | Return Value | Example |
|---|---|---|---|
| Configuration | Sets builder state | Builder (self) | with_title, add_header |
| Validation | Checks configured state | Builder or raises | validate_email |
| Transformation | Modifies accumulated state | Builder (self) | normalize, sanitize |
| Terminal | Creates final product | Product object | build, create, execute |
| Reset | Clears builder state | Builder (self) | reset, clear |
Pattern Comparison
| Pattern | Purpose | Required Fields | Validation | Complexity |
|---|---|---|---|---|
| Classic Builder | Multiple representations | Interface defines | At each step | High |
| Fluent Builder | Readability | Optional | Deferred | Medium |
| Step Builder | Enforced order | All required | Step transitions | High |
| Test Builder | Test data | All optional | Minimal | Low |
| DSL Builder | Domain language | Context dependent | Flexible | Medium |
When to Use Builder Pattern
| Scenario | Use Builder | Alternative |
|---|---|---|
| 5+ parameters | Yes | Constructor still manageable |
| Many optional params | Yes | Keyword arguments |
| Complex construction | Yes | Factory method |
| Multiple representations | Yes | Abstract factory |
| Construction order matters | Yes with steps | Builder unnecessary |
| Immutable products | Yes | Constructor with validation |
| DSL requirements | Yes | No better alternative |
| Simple object | No | Direct constructor |
Common Builder Idioms
| Idiom | Implementation | Use Case |
|---|---|---|
| Method chaining | Return self | Fluent interface |
| Block configuration | Yield self to block | Grouped settings |
| Validation methods | Check before setting | Early error detection |
| Default values | Set in build method | Optional parameters |
| Named builders | Class methods | Common configurations |
| Immutable products | Copy state in build | Thread-safe products |
| Builder reset | Clear internal state | Builder reuse |
| Nested builders | Builders for components | Complex hierarchies |