Implement Strategy using a lambda — when is the classic i… — Cracked Java
// Object-Oriented Programming · Behavioral Design Patterns
MidCodingAmazon

Implement Strategy using a lambda — when is the classic interface still better?

A Strategy is a single-method interface — a SAM. Since Java 8, you can supply one with a lambda instead of a named class, which collapses three files into one expression. The classic interface form earns its keep when the strategy needs state, multiple methods, identity, or DI metadata.

Strategy as a lambda

@FunctionalInterface
public interface PricingStrategy {
    BigDecimal price(Cart cart);
}

public class Checkout {
    private final PricingStrategy pricing;
    public Checkout(PricingStrategy p) { this.pricing = p; }
    public BigDecimal total(Cart cart) { return pricing.price(cart); }
}

// Lambda strategies — no class needed
var standard = new Checkout(cart -> cart.subtotal());
var withTax  = new Checkout(cart -> cart.subtotal().multiply(new BigDecimal("1.08")));
var bf       = new Checkout(cart -> cart.subtotal().multiply(new BigDecimal("0.75")));

Three pricing strategies, zero new classes. The @FunctionalInterface annotation is documentation: it tells readers (and the compiler) that you intend this to be lambda-targetable.

When the classic interface wins

1. The strategy holds state

A RetryStrategy with a backoff multiplier, a max attempt count, and a circuit-breaker reference can't fit cleanly in a lambda. You'd capture three locals, and the resulting closure is harder to test and impossible to inspect.

public final class ExponentialBackoffRetry implements RetryStrategy {
    private final int maxAttempts;
    private final Duration initialDelay;
    private final double multiplier;
    public ExponentialBackoffRetry(int max, Duration init, double mult) { /* ... */ }
    public <T> T execute(Callable<T> task) { /* loop with delays */ return null; }
}

2. The strategy has multiple methods

A CompressionStrategy with compress(byte[]) and decompress(byte[]) isn't a SAM. Lambdas don't apply — you need a real interface (or default methods that delegate to two SAMs, which is uglier than just naming the class).

3. You need a stable identity

If you cache strategies in a Map<String, Strategy>, route by type, or compare them for equality, lambdas are anonymous and unsuitable. Each cart -> cart.subtotal() is a fresh, unequal instance.

4. The DI container needs to inject it

Spring wires beans by type and name. A @Component public class StandardPricing implements PricingStrategy is discoverable; a lambda created in a @Bean method works but loses lifecycle hooks (@PostConstruct, @PreDestroy) and AOP eligibility.

5. The strategy needs documentation or testability

Lambdas don't carry names. Stack traces show Checkout.lambda$method$0 not StandardPricing.price. For complex business rules, a named class with Javadoc and dedicated tests is worth the boilerplate.

A heuristic

Use a lambda when...Use a class when...
The body is one expression or a few linesThe body needs helper methods
There's no state beyond captured finalsThe strategy is stateful
One-off, defined where it's usedReused across modules
Behavior is the valueIdentity matters

Mark your status