Optivem Journal

Optivem Journal

Clean Architecture

Clean Architecture: How to Implement the Strategy Pattern

Where the interface lives, who owns the rules, and how it's wired

Valentina Jemuović's avatar
Valentina Jemuović
Oct 09, 2026
∙ Paid

📅 Register (free): Spec-Driven Development with ATDD & AI (Oct 14)

Register now


The Strategy pattern is usually introduced with a diagram:

Context
  └── Strategy
        ├── Strategy A
        ├── Strategy B
        └── Strategy C

Useful. But in a real application, the more important question is:

Where should those strategies live in Clean Architecture?

The problem

You’re placing an order, and the shipping cost depends on where it’s going.

public Money calculateShipping(Order order) {
    if (order.country().zone() == ShippingZone.DOMESTIC) {
        return Money.of(5);
    }

    return Money.of(15);
}

That works. Then the rules grow:

public Money calculateShipping(Order order) {
    var zone = order.country().zone();

    if (zone == ShippingZone.DOMESTIC) {
        return order.subtotal().isAtLeast(Money.of(50))
            ? Money.ZERO
            : Money.of(5);
    } else if (zone == ShippingZone.NORTH_AMERICA) {
        return Money.of(10).add(Money.of(1).multiply(order.itemCount()));
    } else if (zone == ShippingZone.INTERNATIONAL) {
        return Money.of(20)
            .add(Money.of(2).multiply(order.itemCount()))
            .add(Money.of(15));
    }

    throw new IllegalStateException(”Unknown zone: “ + zone);
}

Now the code that places the order knows every shipping rule in the system. Every new market means editing it. Every rate change means editing it.

Step 1: The domain defines the interface

Define what the business needs, not how it’s done:

public interface ShippingPolicy {
    Money calculate(Money subtotal, Quantity quantity);
}

This is something the business needs: “give me the shipping cost for an order of this value and size”. So the domain owns it.

Notice the name: ShippingPolicy, not ShippingStrategy. “Strategy” is the pattern’s name; “policy” is the business’s word for it. The interface is a Strategy, but you name it in the language of the domain.

Step 2: Each rule becomes a class

public final class DomesticShipping implements ShippingPolicy {

    private static final Money FREE_SHIPPING_THRESHOLD = Money.of(50);
    private static final Money FLAT_RATE = Money.of(5);

    @Override
    public Money calculate(Money subtotal, Quantity quantity) {
        if (subtotal.isAtLeast(FREE_SHIPPING_THRESHOLD)) {
            return Money.ZERO;
        }

        return FLAT_RATE;
    }
}
public final class NorthAmericaShipping implements ShippingPolicy {

    private static final Money BASE = Money.of(10);
    private static final Money PER_ITEM = Money.of(1);

    @Override
    public Money calculate(Money subtotal, Quantity quantity) {
        return BASE.add(PER_ITEM.multiply(quantity.value()));
    }
}
public final class InternationalShipping implements ShippingPolicy {

    private static final Money BASE = Money.of(20);
    private static final Money PER_ITEM = Money.of(2);
    private static final Money CUSTOMS_HANDLING = Money.of(15);

    @Override
    public Money calculate(Money subtotal, Quantity quantity) {
        return BASE
            .add(PER_ITEM.multiply(quantity.value()))
            .add(CUSTOMS_HANDLING);
    }
}

Each rule can change without touching the others or the use case.

Step 3: The domain selects the policy by shipping zone

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2026 Valentina Jemuović, Optivem · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture