Table of Contents

Composite Pattern API Reference

Complete API documentation for the Composite pattern in PatternKit.

Namespace

using PatternKit.Structural.Composite;

Composite<TIn, TOut>

Tree structure where leaves compute directly and nodes fold child results.

public sealed class Composite<TIn, TOut>

Type Parameters

Parameter Description
TIn Input type for execution
TOut Output type (uniform for leaves and nodes)

Delegates

public delegate TOut LeafOp(in TIn input);
public delegate TOut Seed(in TIn input);
public delegate TOut Combine(in TIn input, TOut acc, TOut childResult);
Delegate Description
LeafOp Leaf computation from input
Seed Initial accumulator for node
Combine Fold function for child results

Static Methods

Method Returns Description
Leaf(LeafOp op) Builder Create leaf builder
Node(Seed seed, Combine combine) Builder Create node builder

Instance Methods

Method Returns Description
Execute(in TIn input) TOut Execute the composite tree

Example

var composite = Composite<int, int>
    .Node(static (in int _) => 0, static (in int _, int a, int r) => a + r)
    .AddChildren(
        Composite<int, int>.Leaf(static (in int x) => x),
        Composite<int, int>.Leaf(static (in int _) => 10))
    .Build();

var result = composite.Execute(5); // 5 + 10 = 15

Composite<TIn, TOut>.Builder

Builder for configuring leaves and nodes.

public sealed class Builder

Methods

Method Returns Description
AddChild(Builder child) Builder Add child (no-op on leaves)
AddChildren(params Builder[] children) Builder Add multiple children
Build() Composite<TIn, TOut> Build immutable composite

Semantics

  • Leaf vs Node: Determined at creation (Leaf(...) vs Node(...))
  • Child order preserved: Children execute and combine in registration order
  • Leaves ignore children: AddChild/AddChildren are no-ops on leaves
  • Empty node: Returns Seed(input) with no children

Execution Flow

Leaf Execution

sequenceDiagram
    participant C as Caller
    participant L as Leaf

    C->>L: Execute(input)
    L->>L: LeafOp(input)
    L-->>C: Return result

Node Execution

sequenceDiagram
    participant C as Caller
    participant N as Node
    participant Ch as Children

    C->>N: Execute(input)
    N->>N: acc = Seed(input)
    loop Each child
        N->>Ch: child.Execute(input)
        Ch-->>N: childResult
        N->>N: acc = Combine(input, acc, childResult)
    end
    N-->>C: Return acc

Thread Safety

Component Thread-Safe
Builder No - single-threaded configuration
Composite<TIn, TOut> Yes - immutable after build
Execute Yes - no shared mutable state

Implementation Notes

  • Children stored as arrays at Build() time
  • Execution is tight loop, no LINQ or reflection
  • in TIn parameters avoid struct copies
  • Static lambdas recommended to avoid closures

Complete Example

using PatternKit.Structural.Composite;

// Define a pricing structure
public class PriceCalculator
{
    private readonly Composite<OrderContext, decimal> _calculator;

    public PriceCalculator()
    {
        _calculator = Composite<OrderContext, decimal>
            .Node(
                static (in OrderContext _) => 0m,
                static (in OrderContext _, decimal acc, decimal amount) => acc + amount)
            .AddChildren(
                // Base price
                Composite<OrderContext, decimal>.Leaf(
                    static (in OrderContext c) => c.Items.Sum(i => i.Price * i.Quantity)),
                // Tax
                Composite<OrderContext, decimal>
                    .Node(
                        static (in OrderContext _) => 0m,
                        static (in OrderContext _, decimal acc, decimal tax) => acc + tax)
                    .AddChildren(
                        // State tax
                        Composite<OrderContext, decimal>.Leaf(
                            static (in OrderContext c) =>
                                c.Items.Sum(i => i.Price * i.Quantity) * c.StateTaxRate),
                        // Local tax
                        Composite<OrderContext, decimal>.Leaf(
                            static (in OrderContext c) =>
                                c.Items.Sum(i => i.Price * i.Quantity) * c.LocalTaxRate)),
                // Shipping
                Composite<OrderContext, decimal>.Leaf(
                    static (in OrderContext c) => c.ShippingMethod switch
                    {
                        "express" => 15.99m,
                        "standard" => 5.99m,
                        "free" => 0m,
                        _ => 9.99m
                    }),
                // Discounts (negative)
                Composite<OrderContext, decimal>
                    .Node(
                        static (in OrderContext _) => 0m,
                        static (in OrderContext _, decimal acc, decimal discount) => acc + discount)
                    .AddChildren(
                        // Coupon discount
                        Composite<OrderContext, decimal>.Leaf(
                            static (in OrderContext c) =>
                                c.CouponDiscount.HasValue ? -c.CouponDiscount.Value : 0m),
                        // Member discount
                        Composite<OrderContext, decimal>.Leaf(
                            static (in OrderContext c) =>
                                c.IsMember ? -(c.Items.Sum(i => i.Price * i.Quantity) * 0.05m) : 0m)))
            .Build();
    }

    public decimal Calculate(OrderContext context) =>
        _calculator.Execute(context);
}

public record OrderContext(
    List<OrderItem> Items,
    string ShippingMethod,
    decimal StateTaxRate,
    decimal LocalTaxRate,
    bool IsMember,
    decimal? CouponDiscount);

public record OrderItem(string Sku, decimal Price, int Quantity);

// Usage
var context = new OrderContext(
    Items: new List<OrderItem>
    {
        new("SKU001", 29.99m, 2),
        new("SKU002", 49.99m, 1)
    },
    ShippingMethod: "standard",
    StateTaxRate: 0.06m,
    LocalTaxRate: 0.02m,
    IsMember: true,
    CouponDiscount: 10m);

var calculator = new PriceCalculator();
var total = calculator.Calculate(context);
// Base: 109.97 + State tax: 6.60 + Local tax: 2.20 + Shipping: 5.99 - Coupon: 10 - Member: 5.50 = 109.26

Comparison with Other Patterns

Pattern When to Use
Composite Part-whole hierarchies with uniform operations
Chain Sequential processing with stop/continue
Strategy First-match branching among operations
Visitor Operations across different node types

See Also