Builder Pattern Guide
Comprehensive guide to using the Builder pattern in PatternKit.
Overview
Builder separates the construction of a complex object from its representation. PatternKit provides multiple builder variants for different use cases.
flowchart TD
subgraph Builders
MB[MutableBuilder]
CO[Composer]
CB[ChainBuilder]
BB[BranchBuilder]
end
MB -->|Mutations + Validation| M[Mutable Objects]
CO -->|Transforms + Validation| F[Functional Pipelines]
CB -->|Handler Chains| C[Processing Chains]
BB -->|Predicate Routing| R[Routers]
Getting Started
Installation
using PatternKit.Creational.Builder;
MutableBuilder
Configure mutable objects with mutations and validations:
var person = MutableBuilder<Person>
.New(() => new Person())
.With(p => p.Name = "Ada")
.With(p => p.Age = 30)
.Require(p => string.IsNullOrEmpty(p.Name) ? "Name required" : null)
.Build();
Composer
Functional composition with transforms:
var pipeline = Composer<string>
.Create()
.Transform(s => s.Trim())
.Transform(s => s.ToLower())
.Validate(s => s.Length > 0 ? null : "Empty string")
.Build();
var result = pipeline.Apply(" HELLO "); // "hello"
Core Concepts
Mutations (With)
Mutations are in-place modifications applied in registration order:
var builder = MutableBuilder<Widget>
.New(() => new Widget())
.With(w => w.Steps.Add("A")) // First
.With(w => w.Steps.Add("B")) // Second
.With(w => w.Steps.Add("C")); // Third
var widget = builder.Build(); // Steps = ["A", "B", "C"]
Validations (Require)
Validations run after mutations, fail-fast on first error:
var builder = MutableBuilder<Config>
.New(() => new Config())
.With(c => c.Port = -1)
.Require(c => c.Port > 0 ? null : "Port must be positive") // Fails
.Require(c => c.Host != null ? null : "Host required"); // Not reached
// Throws InvalidOperationException: "Port must be positive"
builder.Build();
Builder Reuse
Builders can produce multiple instances:
var builder = MutableBuilder<Counter>
.New(() => new Counter());
var c1 = builder.Build(); // Fresh instance
builder.With(c => c.Value++);
var c2 = builder.Build(); // Value = 1
builder.With(c => c.Value++);
var c3 = builder.Build(); // Value = 2
Common Patterns
Configuration Builder
public class AppConfigBuilder
{
private readonly MutableBuilder<AppConfig> _builder;
public AppConfigBuilder()
{
_builder = MutableBuilder<AppConfig>
.New(() => new AppConfig())
.Require(c => c.DatabaseConnection != null ? null : "Database connection required")
.Require(c => c.ApiKey != null ? null : "API key required");
}
public AppConfigBuilder WithDatabase(string connectionString)
{
_builder.With(c => c.DatabaseConnection = connectionString);
return this;
}
public AppConfigBuilder WithApi(string key, string baseUrl)
{
_builder.With(c => c.ApiKey = key);
_builder.With(c => c.ApiBaseUrl = baseUrl);
return this;
}
public AppConfigBuilder WithLogging(LogLevel level)
{
_builder.With(c => c.LogLevel = level);
return this;
}
public AppConfig Build() => _builder.Build();
}
// Usage
var config = new AppConfigBuilder()
.WithDatabase("Server=localhost;Database=app")
.WithApi("secret-key", "https://api.example.com")
.WithLogging(LogLevel.Info)
.Build();
Test Data Builder
public class OrderBuilder
{
private readonly MutableBuilder<Order> _builder;
public OrderBuilder()
{
_builder = MutableBuilder<Order>
.New(() => new Order
{
Id = Guid.NewGuid(),
CreatedAt = DateTime.UtcNow,
Status = OrderStatus.Pending
});
}
public OrderBuilder WithCustomer(string name, string email)
{
_builder.With(o => o.CustomerName = name);
_builder.With(o => o.CustomerEmail = email);
return this;
}
public OrderBuilder WithItem(string product, int quantity, decimal price)
{
_builder.With(o => o.Items.Add(new OrderItem
{
Product = product,
Quantity = quantity,
UnitPrice = price
}));
return this;
}
public OrderBuilder WithStatus(OrderStatus status)
{
_builder.With(o => o.Status = status);
return this;
}
public Order Build() => _builder.Build();
}
// Test usage
var order = new OrderBuilder()
.WithCustomer("Alice", "alice@example.com")
.WithItem("Widget", 2, 9.99m)
.WithItem("Gadget", 1, 19.99m)
.WithStatus(OrderStatus.Paid)
.Build();
Fluent Validation Builder
public static class ValidationExtensions
{
public static MutableBuilder<T> RequireNotEmpty<T>(
this MutableBuilder<T> builder,
Func<T, string?> selector,
string fieldName)
{
return builder.Require(obj =>
{
var value = selector(obj);
return string.IsNullOrWhiteSpace(value)
? $"{fieldName} cannot be empty"
: null;
});
}
public static MutableBuilder<T> RequireRange<T>(
this MutableBuilder<T> builder,
Func<T, int> selector,
int min,
int max,
string fieldName)
{
return builder.Require(obj =>
{
var value = selector(obj);
return value >= min && value <= max
? null
: $"{fieldName} must be between {min} and {max}";
});
}
}
// Usage
var user = MutableBuilder<User>
.New(() => new User())
.With(u => u.Name = "Alice")
.With(u => u.Age = 25)
.RequireNotEmpty(u => u.Name, "Name")
.RequireRange(u => u.Age, 18, 120, "Age")
.Build();
Best Practices
Prefer Static Lambdas
Avoid closure allocations:
// Good - static lambda
.With(static p => p.Name = "default")
// Avoid - captures variable
string name = "default";
.With(p => p.Name = name) // Allocates closure
Validate Early
Put critical validations first:
.Require(c => c.Required != null ? null : "Required field missing")
.Require(c => c.Optional?.IsValid() ?? true ? null : "Invalid optional")
Keep Builders Focused
One builder per object type:
// Good - focused builders
var orderBuilder = new OrderBuilder();
var customerBuilder = new CustomerBuilder();
// Avoid - mega-builder
var builder = new EverythingBuilder();
Troubleshooting
"InvalidOperationException: Name required"
A validation failed. Check the message for which validation and fix the configuration.
Mutations not applied
Ensure Build() is called after all With() calls. Builders are mutable.
Unexpected state
Builders accumulate state. Create a new builder for fresh instances:
// Each test should use a new builder
var builder = new OrderBuilder();
var order = builder.Build();