Skip to content
Back to skills

Gof Strategy

ASecurity

Strategy in modern Java, separated into three things that are usually conflated: the design concept (an algorithm varies), the classical class hierarchy, and the lambda or functional interface that expresses it today. Covers when a function value is enough and when a named type earns its keep, selecting a strategy by key instead of an if-else chain, the trap of strategies that differ only in constants and may be configuration, how shared state changes concurrency obligations, and the contract...

  • 2 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 19, 2026
developmentrustgojavaexpressspringtestingperformance

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 29, 2026

npx -y skills add robsonkades/agent-skills --skill gof-strategy --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Gof Strategy?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Gof Strategy
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/robsonkades-gof-strategy/badge)](https://www.skillsdirectory.com/skills/robsonkades-gof-strategy)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: gof-strategy
description: >
  Strategy in modern Java, separated into three things that are usually conflated: the design
  concept (an algorithm varies), the classical class hierarchy, and the lambda or functional
  interface that expresses it today. Covers when a function value is enough and when a named type earns its
  keep, selecting a strategy by key instead of an if-else chain, the trap of strategies that
  differ only in constants and may be configuration, how shared state changes concurrency
  obligations, and the contract test implementations can share. Use when an algorithm must vary
  at runtime, when a switch over a type code keeps growing, when a class hierarchy exists whose
  members are one-line methods, or when strategy classes differ only in a rate or a threshold.
  Does not cover lifecycle-governed legal operations and transitions (gof-state), separating an
  abstraction from independently evolving implementations (gof-bridge), an algorithm skeleton
  with varying steps (gof-template-method), or subclass-controlled creation hooks (gof-factory-method).
---

# Strategy

## Purpose

Let an operation's algorithm vary independently of the code that uses it. The design concept can be sound
while a class hierarchy, lambda, enum strategy, table or direct branch is the better mechanism.

Three things share the name, and separating them settles most arguments:

```text
The concept        "This algorithm varies; callers rely on a shared
                   contract." Selection may still be explicit.

The class          interface + implementations. Useful when related
hierarchy          operations, dependencies or lifecycle belong together.

The function       a lambda or method reference passed where the
value              algorithm is needed. Another expression of the same
                   concept when its single operation fits the contract.
```

A `Comparator` lambda can express Strategy. Recognise the intent before choosing the mechanism;
neither a lambda nor a named implementation needs a dedicated selector class.

Start with consumer calls, required inputs/results/failures, the extension model and existing
selection/configuration evidence. Reuse accepted contracts before asking about material gaps;
keep an adequate branch or function. A changing policy reference alone does not justify a new pattern.

## When it is the answer

```text
An operation has more than one algorithm and the choice is made at
runtime — by configuration, by a caller, by data
        → Strategy, in whichever mechanism fits.

A switch over a type code keeps growing, and each branch is a
self-contained calculation
        → Strategy, keyed by that code.

Callers must be able to supply their own algorithm
        → Strategy through a contract suited to callers: a functional
          interface for one operation, or a cohesive type when related
          operations or lifecycle require it. Preserve published contracts.
```

## When it is not

- **One algorithm exists and there is no boundary/ownership reason to abstract it.** An interface
  may be premature, while a published port, platform SPI or testable external policy can justify
  one implementation (`gof-pattern-thinking`).
- **The variants differ only in data and share identical rules.** Prefer typed configuration.
  Separate named policies can still be warranted when validation, authorization, rollout,
  compatibility or lifecycle differs.
- **The behavior governs lifecycle-dependent operations and valid transitions.** That is State
  (`gof-state`). Interchangeable policies may still be selected internally or adapt to current data/load.
- **Only part of an algorithm varies, inside a fixed sequence.** Compare Template Method with
  a fixed method taking the varying part as a function, preserving existing extension contracts
  (`gof-template-method`).
- **The branches are not self-contained.** If each `switch` arm mutates shared state and depends
  on the others, extracting them into strategies moves a tangle rather than resolving it.

## Lambda or named type?

```text
A lambda / method reference is enough when:
    one operation; captured dependencies and lifetime have clear contracts;
    selection/metadata can live in a registration rather than the implementation

A named type earns its place when:
    related operations and invariants are clearer together in one implementation
      (apply and supports(...), for example)
    key/metadata and related operations belong together for cohesion
    its injected dependencies or implementation are clearer as a class
    it must appear in stack traces, thread dumps and metrics by name
    it has its own tests and its own reason to change
```

Both are Strategy. The failure is not choosing the "wrong" one — it is building a five-class
hierarchy when three lambdas would do, or scattering anonymous lambdas that nobody can find when
the calculation misbehaves in production.

## Selection

Examples are partial Java 17 snippets with application types. Sealed classes are standard in 17;
type-pattern switch is final in 21 (earlier supported releases require preview). Inspect the target
toolchain and framework configuration; no upgrade or new dependency is implied.

```java
// what grows badly
if (code.equals("FLAT")) return flat(order);
else if (code.equals("TIERED")) return tiered(order);
else if (code.equals("WEIGHT")) return byWeight(order);
// ... and the else branch, which returns zero and nobody noticed

// keyed lookup, with the failure defined
private final Map<ShippingMethod, ShippingCost> byMethod;

ShippingCost costFor(ShippingMethod method) {
    var strategy = byMethod.get(method);
    if (strategy == null) throw new UnsupportedShippingMethod(method, byMethod.keySet());
    return strategy;
}
```

Spring can inject eligible registered beans as a list or string-keyed map; classpath presence alone
does not register every implementation. Conditions, qualifiers and scanning affect the set, so an
accidental extra bean silently joins it and a missing one silently does not. Build the map from an
explicit key the strategy or registration declares, and fail at startup if a key is duplicated or a required key is
absent.

For a closed key set, compare an enum or compatible exhaustive switch with a validated registry.
Compilation checks known variants, not null inputs, binary evolution or calculation failures.

## Decision rules

```text
IF strategies differ only in values
THEN first model typed validated configuration. Keep named strategies only when the
     values carry distinct policy ownership, compatibility or behavior contracts.

IF a strategy holds mutable state and is shared
THEN define synchronization, confinement or immutable snapshots. Stateless strategies
     are easiest to share, but stateful incremental algorithms are valid when lifetime
     and thread-safety are part of the contract.

IF strategy state belongs to one operation
THEN select a factory or explicitly confined instance; a registry of instances reuses them.
     Define who creates and releases owned resources, including asynchronous completion.
     A lambda capture or dependency-injection scope name does not establish this lifetime.

IF a strategy or its configuration can change during an operation
THEN define the consistency boundary. Where one coherent policy version is required, capture
     the selected implementation and configuration together for that operation. Atomic reference
     replacement alone neither freezes collaborators nor makes old resources safe to close.

IF selection is by a chain of if-else on a code
THEN compare an exhaustive switch for a closed set with a validated map/registry for
     open contributions. Define unknown/default semantics explicitly.

IF a strategy needs to know whether it applies
THEN compare a cohesive named implementation with a predicate/function registration;
     metadata and applicability need not be abstract methods on the public functional interface.
     Consider Chain of Responsibility if several may
     apply in order (gof-chain-of-responsibility).

IF strategies are selected from data crossing a trust boundary
THEN validate/authorize against the supported registry. Extensible sets need not be
     compile-time closed, but untrusted input must never become an arbitrary class name.

IF a calculation is hard to attribute in profiles/logs
THEN use a named method/type, explicit metric tag, or registration metadata. A whole
     class is one option, not the only diagnostic identity.

IF every strategy needs the same pre- and post-processing
THEN that is a template, and it belongs in the caller once — not
     copied into each strategy.

IF the strategy choice changes system behaviour beyond this call —
partitioning, routing, serialisation
THEN inspect persisted data, in-flight work and old/new coexistence requirements.
     Incompatible placement or format changes need migration; a compatible stateless policy
     change can use a validated configuration rollout
     (message-ordering-and-partitioning).
```

## Cross-cutting checks

- **Concurrency.** A shared strategy needs an explicit synchronization/confinement or immutable-state
  contract for itself and captured/injected collaborators. The recurring bug is a strategy
  accumulating results in a field — possibly missed by a one-call test, but leaking across calls,
  with one request's data appearing in another's (`java-memory-model`).
- **Distribution.** Several of the most consequential strategies in a distributed system are
  chosen by configuration and have system-wide effects: the partitioning strategy determines
  ordering guarantees, the serialisation strategy determines compatibility, the retry policy
  determines amplification under failure, the load-balancing strategy determines tail latency.
  Assess coexistence, rollback and retained effects before choosing a rollout or migration;
  a strategy label alone does not require data migration or a compatibility window
  (`sharding-and-partitioning`, `load-balancing-and-routing`, `retries-and-backoff`).
- **Performance.** Dispatch and inlining depend on receiver profiles, compilation tier and code
  shape rather than a fixed implementation count. Non-capturing lambdas may be cached; capturing
  lambdas can allocate and either form may inline. Inspect profiles/compilation on measured hot paths
  (`jit-inlining-and-escape-analysis`).
- **Testing.** Three levels. Test each strategy against its inputs and declared state/lifecycle;
  strategies are not inherently pure. Test the selector separately, including the
  unknown-key case. And a shared contract test that every implementation must pass, which is what
  stops the fifth strategy from quietly violating an invariant the first four honour.

## Review checklist

- [ ] Variation exists today, or a concrete port/SPI/ownership boundary justifies one implementation
- [ ] Variants justify behavior or a named policy/ownership reason; data-only alternatives were considered
- [ ] Strategy state has explicit immutability, confinement or synchronization semantics
- [ ] Selection matches the extension model; unknown/default semantics are deliberate
- [ ] External keys are validated/authorized against a supported registry
- [ ] Diagnostic identity is available through named methods/types or bounded registration metadata
- [ ] Shared pre/post processing lives in the caller, not duplicated per strategy
- [ ] A contract test runs against every implementation
- [ ] System-wide policy changes have an evidence-based rollout or migration that preserves required contracts

## References

Deliver the variation and contract, chosen mechanism/selector, state ownership and failure behavior,
plus focused checks. Keep performance benefits conditional on measurements.

- [Concept, mechanism and selection](references/concept-mechanism-selection.md) — the three levels
  in detail; lambda against named type with the criteria that decide; selection mechanisms
  compared (map, sealed switch, injected list, `ServiceLoader`) with their failure modes; the
  constants-are-configuration test; state lifetime and replacement; and the shared contract test.
  Read when choosing a mechanism or changing strategy lifetime/configuration.
- [Worked example](references/worked-example.md) — shipping cost calculation taken from a growing
  if-else to lambdas, then to named strategies when logging, metrics and a `supports` check were
  needed, with unknown-method handling, an illustrative shared-state race, and the
  contract test. Read when implementing.

Files in this skill

  • SKILL.md11.5 KB
  • references/concept-mechanism-selection.md10.3 KB
  • references/worked-example.md10.3 KB
  • skill.yaml2.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…