Table of Contents

Prototype Pattern API Reference

Complete API documentation for the Prototype pattern in PatternKit.

Namespace

using PatternKit.Creational.Prototype;

Prototype<T>

Single-source prototype with cloner and optional default mutations.

public sealed class Prototype<T>

Type Parameters

Parameter Description
T The prototype type to clone

Delegates

public delegate T Cloner(in T source);

Delegate that clones a source instance into a new instance. The in parameter avoids struct copies.

Static Methods

Method Returns Description
Create(T source, Cloner cloner) Builder Create builder with source and cloner

Instance Methods

Method Returns Description
Create() T Clone with default mutations only
Create(Action<T>? mutate) T Clone with default + per-call mutations

Example

public record Widget { public string Name { get; set; } public int Size { get; set; } }

var proto = Prototype<Widget>
    .Create(new Widget { Name = "base", Size = 1 }, Clone)
    .With(w => w.Size++)
    .Build();

var a = proto.Create();                  // Size=2
var b = proto.Create(w => w.Size += 10); // Size=12

static Widget Clone(in Widget w) => new() { Name = w.Name, Size = w.Size };

Prototype<T>.Builder

Builder for configuring the prototype.

public sealed class Builder

Methods

Method Returns Description
With(Action<T> mutate) Builder Add default mutation applied to every clone
Build() Prototype<T> Build immutable prototype

Semantics

  • Mutations compose: Multiple With calls chain in order
  • Build captures state: Further modifications don't affect built prototypes
  • Cloner required: Must be provided at Create time

Prototype<TKey, T>

Registry of keyed prototype families.

public sealed class Prototype<TKey, T> where TKey : notnull

Type Parameters

Parameter Description
TKey Key type for lookup (e.g., enum, string)
T Prototype type to clone

Delegates

public delegate T Cloner(in T source);

Static Methods

Method Returns Description
Create(IEqualityComparer<TKey>? comparer = null) Builder Create builder with optional comparer

Instance Methods

Method Returns Description
Create(TKey key) T Clone for key, throws if missing
Create(TKey key, Action<T>? mutate) T Clone with per-call mutation
TryCreate(TKey key, out T value) bool Safe creation, returns false if missing

Exceptions

Method Exception Condition
Create InvalidOperationException No mapping for key and no default

Example

enum ShapeKind { Circle, Square }

var shapes = Prototype<ShapeKind, Shape>
    .Create()
    .Map(ShapeKind.Circle, new Circle { Radius = 1 }, Clone)
    .Map(ShapeKind.Square, new Square { Side = 1 }, Clone)
    .Mutate(ShapeKind.Circle, s => s.Color = "red")
    .Default(new Shape { Name = "unknown" }, Clone)
    .Build();

var circle = shapes.Create(ShapeKind.Circle);           // Color="red"
var square = shapes.Create(ShapeKind.Square, s => s.Side = 5);

Prototype<TKey, T>.Builder

Builder for keyed prototype registry.

public sealed class Builder

Methods

Method Returns Description
Map(TKey key, T source, Cloner cloner) Builder Register prototype family for key
Mutate(TKey key, Action<T> mutate) Builder Add default mutation for key
Default(T source, Cloner cloner) Builder Set default prototype family
DefaultMutate(Action<T> mutate) Builder Add mutation to default
Build() Prototype<TKey, T> Build immutable registry

Semantics

  • Map before Mutate: Map must be called before Mutate for each key
  • Last mapping wins: Calling Map with same key replaces previous
  • Default is optional: Without default, missing keys throw
  • Mutations compose: Multiple Mutate calls chain in order

Exceptions

Method Exception Condition
Build InvalidOperationException Mutate called without Map for a key

Execution Order

Single Prototype

sequenceDiagram
    participant P as Prototype
    participant C as Cloner
    participant DM as Default Mutations
    participant PM as Per-Call Mutation

    P->>C: Clone source
    C-->>P: New instance
    P->>DM: Apply default mutations (in order)
    P->>PM: Apply per-call mutation
    P-->>Caller: Return cloned instance

Registry Prototype

sequenceDiagram
    participant R as Registry
    participant L as Lookup
    participant F as Family
    participant C as Cloner

    R->>L: Find family for key
    alt Found
        L-->>R: Family
    else Not found
        alt Has default
            L-->>R: Default family
        else No default
            R-->>Caller: Throw InvalidOperationException
        end
    end
    R->>C: Clone source
    C-->>R: New instance
    R->>F: Apply mutations
    R-->>Caller: Return instance

Thread Safety

Component Thread-Safe
Builder No - single-threaded configuration
Prototype<T> Yes - immutable after build
Prototype<TKey, T> Yes - dictionary is read-only
Create Yes - but cloner/mutations must be

Implementation Notes

  • Single dictionary lookup per keyed Create call
  • Cloner receives in T to avoid struct copies
  • Delegate.Combine for mutation composition
  • No reflection or LINQ in hot path

Complete Example

using PatternKit.Creational.Prototype;

// Define types
public class Enemy
{
    public Guid Id { get; set; }
    public string Name { get; set; } = "";
    public int Health { get; set; }
    public int Damage { get; set; }
    public Vector3 Position { get; set; }
}

// Create registry
public class EnemySpawner
{
    private readonly Prototype<string, Enemy> _enemies;

    public EnemySpawner()
    {
        _enemies = Prototype<string, Enemy>
            .Create(StringComparer.OrdinalIgnoreCase)
            .Map("goblin", new Enemy
            {
                Name = "Goblin",
                Health = 30,
                Damage = 5
            }, Clone)
            .Map("orc", new Enemy
            {
                Name = "Orc",
                Health = 80,
                Damage = 15
            }, Clone)
            .Map("boss", new Enemy
            {
                Name = "Boss",
                Health = 500,
                Damage = 50
            }, Clone)
            .Mutate("boss", e => e.Name = "Elite " + e.Name)
            .Default(new Enemy { Name = "Unknown", Health = 10, Damage = 1 }, Clone)
            .Build();
    }

    public Enemy Spawn(string type, Vector3 position) =>
        _enemies.Create(type, e =>
        {
            e.Id = Guid.NewGuid();
            e.Position = position;
        });

    public bool TrySpawn(string type, Vector3 position, out Enemy enemy)
    {
        if (_enemies.TryCreate(type, out enemy))
        {
            enemy.Id = Guid.NewGuid();
            enemy.Position = position;
            return true;
        }
        return false;
    }

    private static Enemy Clone(in Enemy e) => new()
    {
        Id = e.Id,
        Name = e.Name,
        Health = e.Health,
        Damage = e.Damage,
        Position = e.Position
    };
}

// Usage
var spawner = new EnemySpawner();
var goblin = spawner.Spawn("goblin", new Vector3(10, 0, 5));
var boss = spawner.Spawn("boss", new Vector3(50, 0, 50));

See Also