agentsclimarketplace

Design patterns

Skill nguyenthdat/opencode-manager/registry/skills/design-patterns

Project-scoped OpenCode TUI plugin for grouping and managing MCP servers, custom agent skills, and pinned vendor skill registries.

Install
npx -y skills add nguyenthdat/opencode-manager --skill design-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 13 days oldThe repository was created 13 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Language-agnostic design-pattern selection and implementation for architecture, refactors, reusable abstractions, runtime polymorphism, factories/builders, wrappers, state machines, commands, observers, and other GoF and modern architectural patterns. Applies whenever a non-trivial design in Rust, TypeScript, Python, Go, C#, Kotlin, C, C++, Swift, or any other language needs a pattern decision, or when reviewing pattern-heavy code. Prefer idiomatic native constructs and simpler concrete code first; do not use for naming a pattern without real design pressure.

SKILL.md

20.0 KB, as published. Nobody here has run it

Design Patterns

Use design patterns as a vocabulary for recurring design pressures, not as a target architecture. Prefer the smallest construct the target language offers that makes the required variation, ownership, and invariants explicit. A pattern name is a communication shortcut for a design already justified by pressure — never the goal itself.

This skill is language-agnostic. Pair it with the relevant per-language coding-standards skill (rust-coding, typescript-coding, python-coding, go-coding, csharp-coding, kotlin-coding, c-coding, cpp-coding, swift-coding, etc.) whenever implementation or code review is in scope, so demonstration code gets normalized into that language's production idiom (typed errors instead of stringly-typed failures, no unnecessary cloning/copying, no unsafe global state, proper resource/memory ownership). For architecture-only advice, apply the decision process here without requiring code-quality commands that have no target repository.

Refactoring.Guru-style examples are pedagogical. Preserve their intent, but hold every language implementation to that language's current idiom, not a literal transliteration of a C++/Java original.

Required Workflow

1. Establish the Pressure

Inspect the current code and identify:

  • The concrete duplication, coupling, unstable dependency, runtime variation, state transition, or lifecycle problem.
  • Which axis is expected to change and which must remain stable.
  • Whether variants are closed and known at compile/author time or open and selected at runtime.
  • Ownership, lifetime, thread-safety, cancellation, persistence, and compatibility (semver/API) constraints.
  • A concrete second implementation or near-term requirement. Keep one-off private code concrete unless abstraction clearly reduces risk.

If no recurring pressure exists, do not add a pattern. Record the simpler alternative and continue with concrete code.

2. Try Native Constructs First

Evaluate the language's own idioms before introducing a custom class/trait/protocol hierarchy:

RequirementRustTypeScriptPythonGoC#KotlinCC++Swift
Closed set of variantsenum + exhaustive matchdiscriminated union + exhaustive switch (never check)Enum + match statement, or @dataclass subclassesiota-based typed const + switch on a type/tagsealed record/class + pattern-matching switchsealed class/sealed interface + whentagged struct (enum tag + union payload) + switchstd::variant + std::visit, or a tagged struct + switchenum with associated values + switch
Stateless behavior injectionclosure / Fn boundfunction value / arrow functionfunction or functools.partialfunction value (func type)delegate / Func<T> / lambdafunction type / lambdafunction pointerstd::function / lambda / template callableclosure
Compile-time polymorphismgenerics / impl Trait / associated typesgenerics (structurally typed, erased)typing.Protocol + duck typing (checked statically only)generics (Go 1.18+) or implicit structural interfacesgenerics with constraints (reified via JIT)generics with bounded type parameters (JVM erasure)_Generic (C11) or code generation/macrostemplates / concepts (C++20)generics with protocol conformances
Adaptation / validation wrappernewtype wrapperbranded type / wrapper classthin wrapper class or NewTypedefined type with methodswrapper readonly struct/classvalue class / inline classwrapper struct with named accessor functionswrapper class / strong typedefwrapper struct conforming to a protocol
Async/event coordinationchannel or explicit event enumEventEmitter / Promise / async iteratorasyncio.Queue / callbackchannel (chan) + goroutineChannel<T> / event / IObservable<T>Channel / Flow / coroutinescallback + explicit state machinestd::condition_variable/queue or a reactor libraryAsyncStream / Combine publisher

Use a named pattern only when it communicates the design better than these constructs alone. Read references/catalog.md and the relevant rules/<pattern-id>.md for the pattern-specific decision detail and per-language code.

3. Choose the Dispatch Model

Use the least flexible model that satisfies the requirement, expressed in each language's own terms:

RequirementRustTypeScriptPythonGoC#KotlinCC++Swift
Closed variants, variant-specific dataenum + matchdiscriminated union + switchEnum/dataclass + matchtyped const-iota + type switchsealed record + switch exprsealed class + whentagged union struct + switchstd::variant + std::visitenum w/ associated values + switch
Stateless replaceable behaviorclosure/Fnfunction valuefunction/callablefunc valuedelegate/Func<T>function type/lambdafunction pointerstd::function/lambdaclosure
Compile-time composition, hot pathsgenerics/impl Traitgenerics (erased at compile time)Protocol (static-check only)generics (Go 1.18+)generics (JIT-reified)generics (JVM erasure)macros/inline functionstemplates/conceptsgenerics (specialized)
Runtime choice among a closed setenum/matchswitch on discriminantmatch/isinstance dispatchtype switchswitch expression/pattern matchwhenswitch on enum tagswitch/std::visitswitch
Open/stored heterogeneous runtime implsdyn Traitinterface/abstract class instanceABC subclass instanceinterface valueinterface/abstract class instanceinterface/abstract class instancestruct-of-function-pointers ("manual vtable")virtual base class pointer/referenceprotocol existential (any Protocol)
Cross-task/cross-thread eventstyped channelmessage queue/worker eventsqueue/asynciochannelChannel<T>/message queueChannel/actorexternal message queuethread-safe queue/condition variableCombine/AsyncStream

Before choosing dynamic dispatch (dyn Trait, interface reference, any Protocol, vtable struct), verify object/protocol safety, ownership, lifetime/retain semantics, thread-safety, allocation, and downcasting requirements. Before choosing generics/templates, assess compile time, binary/monomorphization size, and public API exposure.

Libraries should normally preserve a static generic API so callers retain the dispatch choice. An application that must store one runtime-selected implementation may erase the type at its composition boundary (main, DI container, app delegate) without forcing dynamic dispatch on every library consumer.

4. Design Ownership Explicitly

  • Prefer composition and top-down ownership. Inheritance-shaped object graphs are rarely required to express a pattern's intent, in any language.
  • Pass context into operations when it is short-lived; do not store self-referential or permanent references merely to imitate a class diagram.
  • Avoid unnecessary shared mutable state as a default graph-building tool, regardless of whether the language is garbage-collected:
    • Rust: avoid Rc<RefCell<_>>/Arc<Mutex<_>> as a default; prefer ownership transfer, stable IDs, arenas, Weak, or channels. Use OnceLock/LazyLock for one-time immutable init, never static mut. Never hold a lock guard across .await.
    • C++: prefer value types and unique_ptr for sole ownership; reserve shared_ptr for genuine shared lifetime, and break cycles with weak_ptr. RAII owns cleanup; avoid raw owning pointers.
    • C: ownership is a naming/documentation contract (_create/_destroy, "caller frees"). Be explicit about who frees what; avoid ambient globals for anything test-sensitive.
    • Swift: ARC still leaks via retain cycles — use weak/unowned in closures and delegate references. Value types (struct/enum) avoid the problem entirely; prefer them for models.
    • Go: prefer passing values/interfaces over sharing pointers across goroutines; guard genuinely shared state with a sync.Mutex or a channel-owning goroutine, not both.
    • C#/Kotlin/TypeScript/Python (GC-managed): manual ownership ceremony is unnecessary, but unnecessary shared mutable singletons still cause the same problems — hidden coupling, hard-to-test code, and cross-request/cross-test state leakage. Prefer constructor/DI-provided instances scoped to a request, session, or test, not static/module-level mutable singletons.
  • Avoid Singleton by default in every language. Prefer dependency injection from the composition root (main, DI container, app entry point). Where a language needs one-time lazy init (OnceLock/LazyLock in Rust, lazy in Kotlin, module-level constant in Python, static let in Swift, sync.Once in Go), use it only for genuinely immutable state, and never as a substitute for passing a dependency. These mechanisms are not reset hooks — keep resettable test state injected, or use process/test isolation when reset is required.
  • Keep lock/mutex scope bounded in every language that has one (Mutex, synchronized, lock, pthread_mutex_t, GIL-adjacent asyncio.Lock). Define poison/panic, cancellation, reentrancy, and backpressure behavior where relevant.

5. Implement the Pattern Contract

  • Keep interfaces/traits/protocols narrow and consumer-oriented. Seal or otherwise close public extension points when external implementations are not part of the contract.
  • Preserve domain errors and return the language's typed-failure idiom — Result<T, E> (Rust), a typed Either/discriminated error union (TypeScript), a specific exception type (Python/Java/C#/Kotlin/Swift throws), an error return value (Go), or an explicit error/status output parameter (C) — for fallible construction, adaptation, proxying, undo, persistence, and notification. Do not collapse failures into a boolean, a string, or a silently caught exception.
  • Make ordering, short-circuiting, retries, cache invalidation, subscription lifetime, and state transitions explicit.
  • Mark discardable-by-mistake return values so misuse is caught: #[must_use] (Rust), [[nodiscard]] (C++), a linter rule for unused Promises/results (TypeScript), unused-return warnings (Go vet, Kotlin @CheckReturnValue, -Wunused-result in C/C++).
  • Do not add a dependency, framework, or DI container only to obtain a pattern name. Prefer the standard library and existing project abstractions.
  • Do not expose the pattern vocabulary in public names unless it helps users understand the API (FooVisitor, FooFactory are fine when they clarify; FooManager/FooHelper rarely do).

6. Record the Decision

For every introduced or materially changed pattern, add this to the architecture or implementation artifact. For read-only advice without an artifact, return the same decision record in the response:

Pressure: <specific problem and axis of change>
Decision: <pattern or simpler native construct>
Language form: <enum/union | closure | generic/template | interface/protocol/trait object | channel/queue | wrapper>
Ownership: <who owns state and how references/messages/values flow>
Alternatives: <at least one rejected option and why>
Costs: <allocation, dispatch, compile/build time, binary size, locking, API/compatibility>
Invariants: <behavior that tests must enforce>

Naming a pattern without these fields is not a design decision.

Pattern Catalog

Every entry below links to rules/<pattern-id>.md, which contains the full Intent & Pressure discussion, native-construct alternative, and one idiomatic code example per language (Rust, TypeScript, Python, Go, C#, Kotlin, C, C++, Swift).

Creational

  • factory-method — defer creation of one product to a subclass/implementation/function.
  • abstract-factory — create families of related products without naming concrete types.
  • builder — construct a complex value through optional/ordered steps with validation.
  • prototype — copy an existing object instead of rebuilding it from scratch.
  • singleton — guarantee (and usually avoid) a single process-wide instance.
  • object-pool — reuse expensive-to-create objects instead of reallocating them.

Structural

  • adapter — make a foreign interface satisfy a local contract.
  • bridge — vary abstraction and implementation independently.
  • composite — treat individual objects and compositions of objects uniformly.
  • decorator — layer behavior around a shared interface, order-sensitively.
  • facade — expose a small workflow-oriented entry point over a complex subsystem.
  • flyweight — share immutable intrinsic state across many logical instances.
  • proxy — control access to a service behind its own interface.

Behavioral

  • chain-of-responsibility — pass a request through ordered handlers until one handles it.
  • command — turn a request into an object/value that can be queued, logged, or undone.
  • interpreter — represent a small grammar and evaluate sentences in it.
  • iterator — traverse a collection without exposing its representation.
  • mediator — centralize chaotic peer-to-peer coordination in one owner.
  • memento — capture and restore state without exposing internals.
  • observer — notify dependents of state changes without tight coupling.
  • state — let behavior change with an object's internal state.
  • strategy — vary an algorithm independently of the code that uses it.
  • template-method — fix an algorithm's skeleton while letting steps vary.
  • visitor — add operations to a stable set of element types without modifying them.
  • null-object — replace null/None/undefined checks with a neutral, do-nothing implementation.
  • specification — encapsulate and compose a business predicate as a first-class value.

Architectural / Concurrency

  • dependency-injection — supply collaborators from outside instead of constructing them internally.
  • repository — abstract persistence behind a collection-like domain interface.
  • unit-of-work — track and commit a batch of changes as one transaction.
  • cqrs — separate the read model from the write model.
  • event-sourcing — persist state as an append-only sequence of events.
  • pipeline-middleware — compose ordered request/response processing stages.
  • ports-and-adapters — isolate domain logic from infrastructure behind ports (hexagonal architecture).
  • pub-sub — broadcast events to decoupled, possibly-distributed subscribers.
  • circuit-breaker — stop calling a failing dependency to protect the caller and let it recover.

Review Rules

Treat these as BLOCKER when they can affect correctness, soundness, or public compatibility; otherwise report them as WARNING:

  • A pattern solves no demonstrated pressure or duplicates a simpler native construct.
  • Dynamic dispatch, shared mutability, or allocation/heap indirection was introduced without a runtime requirement.
  • Ownership cycles, lock/mutex scope, callback reentrancy, cancellation, or thread-safety are unspecified.
  • Pattern-specific semantics are broken, such as a proxy bypassing policy, a chain continuing after handling, a state machine permitting invalid transitions, or an event-sourced aggregate replaying non-deterministically.
  • Public interfaces/traits/protocols expose accidental implementation details or create an unnecessary compatibility commitment.
  • A Singleton hides a dependency, blocks test isolation, or uses unsynchronized mutable global state.

Do not block merely because code does not use a named pattern. Concrete code is preferred when it is simpler and sufficient.

Verification

Run the relevant per-language coding-standards gates and add tests for the selected pattern's contract:

  • Construction: required fields, incompatible families, validation failures, and copy/clone depth.
  • Pluggable algorithms/backends: one conformance suite across implementations, correct runtime selection, and static callers remaining free of forced type erasure.
  • Wrappers: transparent behavior, error propagation, policy enforcement, and wrapper ordering.
  • Trees and chains: traversal/order, short-circuit behavior, empty cases, and depth/resource bounds.
  • Commands and snapshots: idempotency where required, undo/redo, failed execution, and versioned restore.
  • Events: subscribe/unsubscribe lifetime, reentrancy, slow consumers, backpressure, and delivery guarantees.
  • State: allowed and rejected transitions, exhaustiveness, persistence, and concurrency behavior.
  • Process-wide resources: no unsynchronized global mutation, explicit initialization behavior, and isolated parallel tests without cross-test state leakage.
  • Architectural patterns (CQRS, event sourcing, ports-and-adapters, pub-sub, circuit-breaker): boundary contracts, replay determinism, timeout/half-open transitions, and infrastructure-swap tests (in-memory adapter vs. real adapter).

Use property tests for stable invariants and transition systems when the state space justifies them. Benchmark only when the pattern choice includes a performance claim.

References

  • references/catalog.md — one-page index of all patterns by category, for quick lookup before opening a specific rule file.
  • rules/<pattern-id>.md — full Intent & Pressure, native-construct alternative, per-language implementation, and pitfalls for each pattern.
  • Refactoring.Guru: Design Patterns — source catalog with parallel examples in C++, C#, Go, Java, PHP, Python, Ruby, Rust, Swift, and TypeScript.
  • Gang of Four, Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helm, Johnson, Vlissides) — the canonical creational/structural/behavioral catalog.
  • Rust Design Patterns — Rust-specific idioms and anti-patterns.
  • Joshua Bloch, Effective Java — idiomatic builder, singleton, and immutability guidance that also underlies most Kotlin API design.
  • Kotlin language documentation, "Idioms" — sealed class/when, object, scope functions as pattern building blocks.
  • Brandon Rhodes / Python Patterns (python-patterns.guide) and the standard library abc/typing.Protocol docs — Python-idiomatic pattern implementations.
  • Go proverbs ("accept interfaces, return structs") and the standard library (io.Reader/sort.Interface) as the primary source of Go-idiomatic pattern shapes.
  • Eric Evans, Domain-Driven Design — source for repository, unit-of-work, specification, and ports-and-adapters framing.
  • Martin Fowler, Patterns of Enterprise Application Architecture, and martinfowler.com articles on CQRS and event sourcing.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.