Template Method Pattern
Category: Behavioral
What it is
Template Method defines the skeleton of an algorithm in a base class, allowing specific steps to be customized without changing the overall structure. PatternKit offers two complementary shapes:
- Subclassing API: derive from
TemplateMethod<TContext, TResult>and override hooks. - Fluent API: compose a
Template<TContext, TResult>withBefore/After/OnError/SynchronizedandExecute/TryExecute.
Common traits:
- Generic and type-safe (any context/result types)
- Allocation-light, production-shaped APIs
- Optional synchronization for thread safety
- Clear separation of “when/where” (hooks) and “what” (the main step)
TL;DR (subclassing)
using PatternKit.Behavioral.Template;
public sealed class DataProcessor : TemplateMethod<string, int>
{
protected override void OnBefore(string context)
=> Console.WriteLine($"Preparing to process: {context}");
protected override int Step(string context)
=> context.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
protected override void OnAfter(string context, int result)
=> Console.WriteLine($"Processed '{context}' with result: {result}");
// Optional: serialize concurrent Execute calls
protected override bool Synchronized => true;
}
var processor = new DataProcessor();
var count = processor.Execute("The quick brown fox");
API shape (subclassing)
TemplateMethod<TContext, TResult>(abstract)TResult Execute(TContext context)— callsOnBefore,Step,OnAfterin orderprotected virtual void OnBefore(TContext context)— optional pre-step hookprotected abstract TResult Step(TContext context)— required main stepprotected virtual void OnAfter(TContext context, TResult result)— optional post-step hookprotected virtual bool Synchronized— set totrueto serialize executions
AsyncTemplateMethod<TContext, TResult>(abstract)Task<TResult> ExecuteAsync(TContext context, CancellationToken ct)— callsOnBeforeAsync,StepAsync,OnAfterAsyncprotected virtual ValueTask OnBeforeAsync(TContext context, CancellationToken ct)— optional pre-step hookprotected abstract ValueTask<TResult> StepAsync(TContext context, CancellationToken ct)— required main stepprotected virtual ValueTask OnAfterAsync(TContext context, TResult result, CancellationToken ct)— optional post-step hookprotected virtual bool Synchronized— set totrueto serializeExecuteAsynccalls (usesSemaphoreSlim)
Prefer the fluent siblings when you want multicast hooks, non-throwing
Tryexecution, or quick composition: see Template<TContext, TResult> and AsyncTemplate<TContext, TResult>.
Fluent Example
using PatternKit.Behavioral.Template;
var template = Template<string, int>
.Create(ctx => ctx.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length)
.Before(ctx => Console.WriteLine($"[Before] '{ctx}'"))
.After((ctx, res) => Console.WriteLine($"[After] '{ctx}' -> {res}"))
.OnError((ctx, err) => Console.WriteLine($"[Error] '{ctx}': {err}"))
.Synchronized() // optional
.Build();
if (template.TryExecute("The quick brown fox", out var result, out var error))
Console.WriteLine($"Words: {result}");
else
Console.WriteLine($"Failed: {error}");
Async variants
PatternKit also provides first-class async variants with cancellation and optional synchronization:
Async subclassing example
using PatternKit.Behavioral.Template;
public sealed class AsyncDataPipeline : AsyncTemplateMethod<int, string>
{
protected override bool Synchronized => false; // enable for strict serialization
protected override async ValueTask OnBeforeAsync(int id, CancellationToken ct)
{
Console.WriteLine($"[BeforeAsync] {id}");
await Task.Yield();
}
protected override async ValueTask<string> StepAsync(int id, CancellationToken ct)
{
await Task.Delay(25, ct); // fetch
await Task.Delay(10, ct); // transform
await Task.Delay(5, ct); // store
return $"VAL-{id}";
}
protected override ValueTask OnAfterAsync(int id, string result, CancellationToken ct)
{
Console.WriteLine($"[AfterAsync] {id} -> {result}");
return default; // completed
}
}
var pipe = new AsyncDataPipeline();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
var outVal = await pipe.ExecuteAsync(42, cts.Token);
Async fluent example
using PatternKit.Behavioral.Template;
var tpl = AsyncTemplate<int, string>
.Create(async (id, ct) =>
{
await Task.Delay(15, ct);
if (id < 0) throw new InvalidOperationException("invalid id");
return $"VAL-{id}";
})
.Before((id, ct) => { Console.WriteLine($"[BeforeAsync] {id}"); return default; })
.After((id, res, ct) => { Console.WriteLine($"[AfterAsync] {id} -> {res}"); return default; })
.OnError((id, err, ct) => { Console.WriteLine($"[ErrorAsync] {id}: {err}"); return default; })
.Synchronized() // optional
.Build();
var (ok, result, error) = await tpl.TryExecuteAsync(42);
Guidance
- Prefer async variants for I/O-bound steps or when cancellation needs to flow end-to-end.
- Use
.Synchronized()or overrideSynchronizedonly when shared mutable state demands serialization. - Choose
TryExecuteAsyncwhen you need non-throwing control flow and centralized error observation.
When to Use
- You need a consistent workflow with customizable steps.
- You want to prevent structural drift while enabling tailored behaviors.
- You need optional error handling and synchronization without external plumbing.
Thread Safety
- Subclassing: override
Synchronizedto serializeExecutecalls via a per-instance lock. - Fluent: call
.Synchronized()on the builder to enable a per-instance lock. - For stateless or externally synchronized code, leave synchronization off for maximal concurrency.
Error Handling
- Subclassing: let exceptions bubble; catch externally if needed.
- Fluent: use
TryExecuteto avoid throwing, and.OnError(...)to observe errors.
Related Patterns
- Strategy: swap entire algorithms rather than customizing steps inline.
- Chain of Responsibility: linear rule packs with stop/continue semantics.
- State: behavior that changes with state; Template Method keeps structure fixed.
See Also
- Fluent (sync): Template<TContext, TResult>
- Fluent (async): AsyncTemplate<TContext, TResult>
- Examples: Template Method Demo, Template Method Async Demo
- Refactoring Guru: Template Method — https://refactoring.guru/design-patterns/template-method