Home

/

The Production-Ready Playbook

/

Decorator

Decorator

Chapter 3
Part II
3
min read

Decorator

The problem. You want to add behaviour to an object without modifying it or subclassing it for every combination. An order's price is rarely the base total. Surge multiplies it at peak hours, a promo code knocks money off, and a loyalty tier shaves a percentage, and any of the three can apply at once.

public sealed class SurgePricing(IPricing inner, ISurgeClock clock) : IPricing
{
    public Money PriceFor(Order order) =>
        inner.PriceFor(order) * clock.MultiplierAt(order.PlacedAt, order.Restaurant);
}

public sealed class PromoPricing(IPricing inner, PromoCode code) : IPricing
{
    public Money PriceFor(Order order) => code.ApplyTo(inner.PriceFor(order));
}

Each decorator implements the same IPricing it wraps, so they stack: loyalty around promo around surge around the base total, each unaware of the others.

What it buys you in production. The adjustments stay out of the core total. Base pricing computes the sum of items and nothing else; surge, promo, and loyalty live in their own wrappers you compose at registration, in an order you control. Add a "first order free delivery" rule next month as one more decorator, no edit to the four that already work. This is the in-process cousin of the Proxy and the middleware pipeline you will meet at the component altitude.

Skip-if. You only ever need one adjustment and it will never stack. At that point an extension method or a single inline check is less ceremony. Decorator pays off when behaviours combine, because the alternative is a subclass explosion for every permutation of surge, promo, and loyalty.

In the front-end. The React equivalents are hooks and higher-order components. A withRetry wrapper around a fetch hook, or a useSurgeBanner that layers onto a price display, adds behaviour to a component without rewriting it.

Command

The problem. You want to turn a request into an object, so you can queue it, log it, retry it, or undo it, rather than calling a method and losing the call the instant it returns. Placing an order, cancelling one, adding an item to a cart: each is an intent worth capturing as a value, not a method call that vanishes when it returns.

public interface ICommand
{
    Task Execute(CancellationToken ct);
}

public sealed record PlaceOrder(Guid CartId, Guid CustomerId) : ICommand
{
    public Task Execute(CancellationToken ct) =>
        OrderService.Place(CartId, CustomerId, ct);
}

Once PlaceOrder is a value, you can put it on a queue, write it to a log, hand it to a worker, or hold it for a retry. The invoker and the receiver stop knowing about each other.

What it buys you in production. Command is the object-level seed of everything in the messaging altitude. A PlaceOrder you can serialise is a request you can level off a queue at dinner rush, hand to a competing consumer, or replay after a failure. It also gives you an audit trail for free, because the command is the record of what the customer asked for.

Skip-if. The work runs inline and synchronously, completes immediately, and never needs queuing, logging, or undo. Wrapping a direct method call in a command object then buys you nothing but a class. Reach for it when the request has to outlive the call stack.

In the front-end. The cart is the natural home. Model "add item", "remove item", "apply promo" as command objects and undo falls out for free: keep the executed commands on a stack and an "undo" button reverses the last one. Optimistic UI gets cleaner too, because each command knows how to roll itself back when the server rejects it.

the-pareto-stack-cloud-design-patterns-for-small-teams
the-ladder-of-altitudes
how-to-read-this
object-level-the-patterns-that-earn-their-keep
decorator
state
component-level-structuring-one-service
ports-and-adapters-hexagonal
mediator-the-commandquery-split
data-persistence
optimistic-concurrency
messaging-scale
outbox
resilience-staying-up-when-dependencies-dont
rate-limiting-throttling
timeout-fallback
the-composed-pipeline
observability-diagnostics-seeing-inside-production
metrics-the-four-golden-signals
externalised-configuration
hosting-cloud-agnostic-by-default
sidecar-ambassador
orchestrator-agnostic-deploy
a-reference-service
the-relay-outbox-to-queue
the-payment-saga-charge-pay-out-compensate
the-over-engineering-tax
conclusion-production-ready-deliberately
the-pattern-quick-reference-card
altitude-3-data-persistence
altitude-5-resilience
the-skip-list
full-event-sourcing-for-crud
robert-c-martin-uncle-bob-the-house-authority-for-structure
altitude-2-component
altitude-4-messaging-scale
altitude-6-observability-diagnostics

Download the full PDF for free?

Free download — no account required

Get the PDF
Get the PDF
Related Chapters
Free Download
Get the full PDF
All pages, including all code examples, diagrams, and the appendix reference card.
No spam. Unsubscribe at any time.
Your email won't be shared.
Oops! There's a problem with your request. We're working on fixing it. Please try again later.