Design Patterns skill for Codex
Use a pattern only when change demands it. Codex names the concrete change pressure before reaching for a pattern, then picks the lightest form the language offers, often a typed map of functions instead of a class hierarchy. Refactors start with characterization tests and land as separate behaviour-preserving commits, and over-engineered interfaces with a single implementation get collapsed rather than defended.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- design-patterns Installs to ~/.agents/skills/design-patterns/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install design-patterns The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project design-patterns Installs to .agents/skills/design-patterns/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/design-patterns.zipunzip -o design-patterns.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) design-patterns PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $design-patterns, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Chooses and applies Gang of Four and related design patterns (strategy, factory, adapter, decorator, observer, command, state, builder, template method, facade) only where a concrete change pressure justifies them, and refactors toward or away from patterns in small, test-backed steps. Use when a switch on type keeps growing, a class mixes several variants, code needs a seam for a new provider, someone asks which pattern fits, or an over-engineered abstraction should be collapsed.
Use it when
- A switch or if chain on a type keeps growing or is duplicated
- A new provider, format or variant needs a clean seam
- Someone asks which design pattern fits a problem
- An abstraction has one implementation and only adds indirection
Use something else when
- You need a behaviour-preserving refactor with no pattern in mind: use safe-refactoring
- The code has no tests and you must pin its behaviour first: use legacy-code-characterization
How to check the result
Characterization tests pass unchanged, the type checker rejects a deliberately missing variant, and adding the next variant touches a single file.
The mistake it prevents
Adding interfaces, factories and registries for a variant that never arrives, which costs more to read than the duplication it replaced.
Done well means each pattern in the code answers a change that has happened or is scheduled (a second provider, a third variant, a new output format), the pattern uses the lightest form the language allows (often a function or a map, not a class hierarchy), and the refactor lands as a series of green commits that do not change behaviour.
Before changing anything
- Read
AGENTS.mdand nearby code. Match the codebase’s idioms: a functional TypeScript codebase should not suddenly grow abstract base classes. - Name the change pressure in one sentence: “We are adding a third payment provider”, “Every new report format edits the same 400-line switch”. No pressure, no pattern.
- Check test coverage of the code you will reshape. If it is thin, add characterization tests first (record current outputs for representative inputs).
Smell to pattern
| Smell in the code | Pattern | Lightest form |
|---|---|---|
switch/if chain on a type or mode, repeated in several places | Strategy (or polymorphism) | Map from key to function |
| Calling code must know which concrete class to build | Factory function | One function returning the interface |
| Third-party API shape leaks into your domain | Adapter | Thin wrapper class/module owning the translation |
| Cross-cutting behaviour (caching, retries, metrics) wrapped around calls | Decorator | Higher-order function or middleware |
| Many modules need to react when something happens | Observer / domain events | Typed event emitter or in-process bus |
| Operations must be queued, logged, undone or retried | Command | Plain data object plus a handler |
| Behaviour depends on a lifecycle status with guarded transitions | State | Transition table; state classes only when behaviour differs a lot |
| Constructor with many optional parameters | Builder | Named/keyword arguments or an options object first |
| Same algorithm skeleton, varying steps | Template method | Function taking the varying steps as parameters |
| Callers juggle five subsystem objects for one task | Facade | Module exposing the one task |
Prefer composition to inheritance. Template method and deep hierarchies make sense mainly in frameworks that require subclassing.
When not to use a pattern
- One implementation, no second one on the roadmap: write the concrete code. An interface can be extracted in minutes when the second case arrives.
- The language already does it: iterators, first-class functions, decorators in Python,
Comparable/Comparator, dependency injection containers. - The pattern would need more lines than the duplication it removes.
- Singleton as global state. Use a single instance created at the composition root and passed in; the GoF Singleton makes testing and concurrency harder.
Refactoring toward a pattern
Example: replacing a type switch with a strategy registry.
Before:
function shippingCost(order: Order): number {
switch (order.carrier) {
case "ups": return 5 + order.weightKg * 1.2;
case "fedex": return 7 + order.weightKg * 0.9;
case "dhl": return order.international ? 25 : 9;
default: throw new Error(`unknown carrier ${order.carrier}`);
}
}
After, in small steps:
type CostRule = (order: Order) => number;
const rules = {
ups: (o) => 5 + o.weightKg * 1.2,
fedex: (o) => 7 + o.weightKg * 0.9,
dhl: (o) => (o.international ? 25 : 9),
} satisfies Record<Carrier, CostRule>;
export function shippingCost(order: Order): number {
return rules[order.carrier](order);
}
satisfies Record<Carrier, CostRule> makes the compiler fail when a new carrier is added
to the union without a rule, which the old default branch only caught at runtime.
Steps:
- Characterization tests: one case per branch plus the default/error case. Run green.
- Extract each branch body into a named function in place. Run tests.
- Introduce the registry and route through it. Run tests.
- Delete the switch. Run tests, type check, lint.
- If other switches on the same key exist, route them through the same registry or give the strategy object several methods. Duplicated switches on one key are the strongest signal for this refactor.
Class-based form, when strategies carry state or several methods:
class Exporter(Protocol):
content_type: str
def render(self, report: Report) -> bytes: ...
EXPORTERS: dict[str, Exporter] = {"csv": CsvExporter(), "pdf": PdfExporter(fonts)}
Refactoring away from a pattern
Collapse an abstraction when it has one implementation, its interface mirrors that implementation method for method, and no test uses a fake of it. Inline the interface, merge factory and product, delete the indirection. This is as legitimate as adding one.
Pattern notes that matter in practice
- Adapter: keep the adapter responsible for translating errors and units too (vendor exceptions to domain errors, cents to Money). Leaking vendor exceptions defeats it.
- Decorator: preserve the wrapped interface exactly, including cancellation and async behaviour. A logging decorator that swallows exceptions changes semantics.
- Observer: decide synchronous or asynchronous delivery explicitly; synchronous handlers run inside the publisher’s transaction and their exceptions propagate to it. Unsubscribe or use weak references to avoid leaks in long-lived processes.
- Command: give commands an id if they may be retried; handlers must be idempotent.
- State: write the transition table first (current state, event, guard, next state). Illegal transitions should raise, not silently no-op.
- Builder: validate in
build(), not in setters, so partially built objects never escape.
Gotchas
- Introducing a pattern and changing behaviour in the same commit makes review and bisecting impossible. Keep structure and behaviour changes separate.
- Pattern names in class names (
OrderStrategyFactoryImpl) are noise; name by role (ShippingRates,ExportFormat). - Registries keyed by strings need a single source of truth for keys (enum or union type) or typos fail at runtime.
- Generic “plugin” architectures for one plugin are speculative generality.
Verification
- Characterization tests written before the change pass unchanged after it.
- Type check and lint pass; exhaustiveness (
satisfies, sealed classes,matchwithassert_never) catches a deliberately removed variant. Try removing one and confirm the build fails, then restore it. - Adding the next variant (the change pressure you named) now touches one place. Show this in the summary by listing the files a new variant would edit.
Output template
## Refactor: <area> to <pattern or "collapse">
**Change pressure:** <the concrete reason>
**Before:** <shape, e.g. 3 switches on carrier across 2 files>
**After:** <shape, e.g. registry in shipping/rules.ts, exhaustive over Carrier>
**Adding a new variant now touches:** <files>
**Commits:** 1) characterization tests 2) extract functions 3) registry 4) delete switch
**Tests:** <command, result> What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Design Patterns"
short_description: "Use a pattern only when change demands it"
default_prompt: "$design-patterns This switch on payment provider is copied in four files, refactor it properly" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.