design-pattern-suggestor
Design Pattern Suggestor
A pattern is a named solution to a recurring structural problem. The value is the vocabulary, not the code. The risk is applying a pattern because it's familiar, not because the problem it solves is present.
Problem → pattern (NOT pattern → problem)
Start from the symptom. The pattern is the answer; the problem is the question.
| Symptom | Pattern | Or maybe just… |
|---|---|---|
| Switch on type code, same switch in 3+ places | Strategy / State | …one switch if it's only in one place |
| Constructor has 8 parameters, 5 are optional | Builder | …keyword arguments with defaults |
| Need exactly one of a thing, globally | Singleton | …a module-level variable (in Python/JS) |
| Class hierarchy × orthogonal dimension = class explosion | Decorator / Bridge | …composition with a field |
| Creating objects, but which concrete type depends on runtime data | Factory | …a dict {key: constructor} — often enough |
| Many objects need to know when one object changes | Observer | …a callback list — that IS observer |
| Different interface than what you have, can't change either side | Adapter | Usually correct, rarely over-applied |
| Need undo, or need to queue/log/serialize operations | Command | Usually correct |
| Traversing a composite structure uniformly | Visitor / Iterator | …if the structure rarely changes, just recurse |
The "or maybe just…" column matters
Most pattern misuse is applying GoF ceremony where the language already gives you the mechanism for free:
- Strategy in Python: Functions are first-class.
strategies = {"a": fn_a, "b": fn_b}is the pattern. - Singleton in Python/JS: A module IS a singleton.
import config— done. - Observer in anything with events:
addEventListener, signals, Rx — the pattern is built into the runtime. - Iterator: Every modern language has
yield/ generators. You don't writehasNext()/next().
If the "or maybe just" fits, use it. The pattern is the fallback for languages that lack the mechanism.
Anti-pattern detection
Sometimes the finding is "you're using a pattern wrong":
| Anti-pattern | Symptom |
|---|---|
| Singleton-as-global | Singleton exists to avoid passing a dependency — hidden coupling, untestable |
| Factory-for-one | UserFactory.create() returns new User() — the factory adds nothing |
| Observer soup | 10 observers, firing order matters, some observers trigger other observers — you've built an implicit state machine |
| Premature Strategy | One concrete strategy. The interface exists "in case we need another." → speculative generality |
| Visitor on a stable hierarchy | The hierarchy never changes; visitor's indirection buys nothing |
Worked example
Symptom: "Every time we add a payment method we have to edit process_payment() and validate_payment() and refund_payment() — three switches on method_type."
Match: Same switch, 3 places, on a type code → Strategy.
Check the "or maybe just": What language? Java. No first-class functions pre-lambdas (or the team doesn't use them). Strategy is the idiomatic fit.
Recommendation:
Three switches on
method_type, growing with every new payment method → Strategy pattern.interface PaymentMethod { void process(Payment p); ValidationResult validate(Payment p); void refund(Payment p); } class CreditCard implements PaymentMethod { ... } class PayPal implements PaymentMethod { ... }New payment method → one new class, zero edits to existing switches.
Counter-check: Do
process/validate/refundalways change together when a method is added? Ifvalidateis actually the same for 4 of 5 methods, Strategy will force you to duplicate it. Consider: Strategy forprocess/refund, a singlevalidatewith a small type check.
Do not
- Do not suggest a pattern without stating the problem it solves here. "Use Factory" is cargo cult. "Use Factory because which concrete class depends on a config value you don't know at compile time" is a reason.
- Do not apply Java-idiom patterns to languages that don't need them. Strategy in a language with first-class functions is just "pass a function."
- Do not add a pattern for a problem you might have. One concrete class? No Strategy. One way to build it? No Builder. YAGNI.
- Do not suggest Visitor unless the type hierarchy is closed and the operations are open. If you're adding types more often than operations, Visitor fights you.
Output format
## Symptom
<the structural problem — what hurts>
## Pattern fit
<pattern> — <why it matches this specific symptom>
## Or maybe just…
<simpler alternative if one exists — and when to prefer it>
## Sketch
<minimal code showing the pattern applied to THIS case, not a textbook example>
## When NOT to
<conditions under which this recommendation would be wrong>
More from santosomar/general-secure-coding-agent-skills
dependency-resolver
Diagnoses and resolves package dependency conflicts — version mismatches, diamond dependencies, cycles — across npm, pip, Maven, Cargo, and similar ecosystems. Use when install fails with a resolution error, when two packages require incompatible versions of a third, or when upgrading one dependency breaks another.
4ci-pipeline-synthesizer
Generates CI pipeline configs by analyzing a repo's structure, language, and build needs — GitHub Actions, GitLab CI, or other platforms. Use when bootstrapping CI for a new repo, when porting from one CI to another, when the user asks for a pipeline that builds and tests their project, or when wiring in security gates.
3api-design-assistant
Reviews and designs API contracts — function signatures, REST endpoints, library interfaces — for usability, evolvability, and the principle of least surprise. Use when designing a new public interface, when reviewing an API PR, when the user asks whether a signature is well-designed, or when planning a breaking change.
2code-refactoring-assistant
Executes refactorings — extract method, inline, rename, move — in small, behavior-preserving steps with a test between each. Use when the user wants to restructure working code, when cleaning up after a feature lands, or when a smell has been identified and needs fixing.
2test-driven-generation
Generates code test-first — writes a failing test from a requirement, then generates the minimal code to pass it, then refactors, in strict red-green-refactor cycles. Use when building new features where the spec is clear, when the design is uncertain and you want tests to drive it, or when you need high confidence in coverage from the start.
2code-translation
Translates a single function or small code unit between programming languages, mapping idioms and preserving observable behavior. Use when porting one function, when the user pastes code and asks for it in another language, or as the per-unit primitive for larger migrations.
1