SKILL.md
Template Method Pattern (TypeScript)
Intent
Define a stable algorithm skeleton in a base class and let subclasses override selected steps or hooks without changing the overall order.
When to use
- Import pipelines (CSV/PDF/DOC) with shared sequence but different parsing.
- ETL workflows with common stages and varied implementations.
- Report generation pipelines with shared fetch/deliver steps.
- Payment processing flows (validate -> authorize -> capture) with varied details.
- Background job runners with consistent lifecycle hooks.
- Notification rendering pipelines with per-channel formatting.
- Parsing/normalization flows with shared structure.
- ML/data-prep pipelines that share ordering but vary transforms.
When NOT to use
- You need runtime swapping between algorithms (Strategy).
- Behavior changes due to lifecycle/state transitions (State).
- You need queue/log/undo or remote execution of requests (Command).
- Variations are data-only parameters or configuration.
- Only a couple of variants with low duplication; a shared function is clearer.
- Excessive subclassing would create a fragile base class.
- The base class would need too much IO knowledge.
- The workflow is not stable enough to justify a skeleton.
Mental model
Base class owns the workflow and ordering; subclasses override variable steps; hooks provide optional extension points.
Recommended TS shapes
- Abstract base class with
run()template method callingprotectedsteps. - Hooks as
protected beforeX()/afterX()no-ops. - "Final-ish" enforcement:
public run()calls aprivate doRun(); subclasses override only steps.
Example 1: Document import pipeline (CSV/DOC/PDF)
Steps: load -> parse -> normalize -> validate -> persist -> report. Base provides normalization/validation.
Example 2: Payment processing flow (Card/BankTransfer)
Steps: validate -> authorize -> capture -> receipt, with a hook around authorize.
Example 3: Report generation (Summary/Detailed)
Steps: fetch -> compute -> format -> deliver. Base handles fetch + deliver; subclasses override compute/format.
Testing strategy (pragmatic)
- Base-class contract tests using a fake subclass.
- Per-subclass tests for overridden steps.
- Verify ordering with an in-memory trace.
- Ensure hooks are optional and no-op by default.
Common pitfalls
- Too many steps making the base hard to understand.
- Overriding the template method itself.
- Calling overridable methods from constructors.
- Leaking shared mutable state across subclasses.
- Fragile base class changes that break subclasses.
- Mixing selection logic with template orchestration.
- Tight coupling to IO inside the base class.
- Too many tiny subclasses that differ only by data.
- LSP surprises when subclasses "disable" default steps.
- Using Template Method when Strategy would be simpler.
Checklist for refactors
- Identify duplicated workflows across implementations.
- Define clear step boundaries and names.
- Move the skeleton to a base class.
- Make variable steps
protectedorabstract. - Add hooks for optional behavior.
- Migrate variants one by one.
- Remove client conditionals once subclasses exist.
- Add ordering tests and per-variant tests.
Output expectations
When invoked, produce: base abstract class, clear step methods + hooks, 2-4 concrete subclasses, and tests/examples demonstrating ordering and overrides.