# When a policy interface is not worth its file

Strategy is the most over-applied pattern in an LLD round, and the standard is built to notice.
D3 level 3 reads *"the seam set is minimal — no speculative interface with a single implementation
and no foreseeable second one"*. So a second `PricingPolicy`-shaped interface with one
implementation does not add to your score. It subtracts.

## The bad example, in the same file you have been reading

`PricingPolicy.startedHours` is the rounding rule: any part of an hour is a whole hour, and a
zero-length stay is one. It is a static method on the interface, deliberately. Now take the lesson
from `contrast/` one step further than it goes:

```java
public interface RoundingPolicy {
    long billableUnits(Duration stay);
}

public final class StartedHourRounding implements RoundingPolicy {
    @Override
    public long billableUnits(Duration stay) {
        long seconds = Math.max(0L, stay.toSeconds());
        return Math.max(1L, (seconds + 3599L) / 3600L);
    }
}
```

Then every tariff has to be handed one:

```java
public TieredPricing(RoundingPolicy rounding) {
    this.rounding = Objects.requireNonNull(rounding, "rounding");
}
```

And then, because two constructor arguments feel like assembly work, `PricingPolicyFactory`
arrives to build the pair. STANDARD v1.0 names this exact move: *"A `FooFactoryProvider` for one
concrete type is not sophistication."*

## What it costs, item by item

**Two files that hold one arithmetic expression between them.** `RoundingPolicy.java` and
`StartedHourRounding.java` exist so that `(seconds + 3599L) / 3600L` can be replaced by something
nobody has asked for.

**A constructor parameter with exactly one possible value.** Every tariff now takes a
`RoundingPolicy`, and every call site passes `new StartedHourRounding()`. A parameter that only
ever takes one value is a parameter that reads as a question and answers itself.

**Four files to answer one question at 1am.** A stay of 61 minutes billed 1000 and someone wants to
know why. With the seam: `ExitGate.charge` to `TieredPricing.feeMinor` to `RoundingPolicy` to
`StartedHourRounding`, and the answer is one line at the end of it. Without it: `ExitGate.charge`
to `TieredPricing.feeMinor` to `PricingPolicy.startedHours`, and the last hop is a static method in
a file you already had open.

**A lower D3 score, with a tag attached.** The failure tag is
`over-engineered (premature interface)`, and `server/lib/lessons.mjs` routes it straight back to
this file: `'over-engineered': { item: 'B1', stage: 'when-not' }`. A drill that earns it sends you
here, not to the worked example.

**And the measured payoff on the change that actually arrived is zero.** Curveball 02 replaced the
whole tariff and its requirement says, in one sentence, *"Rounding does not change."* Both
`FlatHourlyPricing` and `TieredPricing` call `PricingPolicy.startedHours`, and neither of them
would have called a `RoundingPolicy` differently. The seam absorbs nothing, because nothing moved
along that axis.

## Two implementations that should have been one

There is a second way to over-apply this, and it is subtler than the extra interface. If the only
difference between two implementations is the numbers, the variation is data and not behaviour, and
the answer is one class taking a table.

Look at what makes the corpus's two tariffs genuinely two classes. `FlatHourlyPricing.feeMinor` is
`rateFor(type) * startedHours(stay)`, a single multiplication. `TieredPricing.feeMinor` walks hour
by hour, because hour 1 is free and hour 4 costs something different from hour 3. Those are
different shapes of calculation, not two settings of one.

Had the new tariff been *"the same flat rates, doubled at weekends"*, a second class would be the
wrong answer. `new FlatHourlyPricing(weekendRates)` is the right one, and the seam you already have
carries it.

## The threshold, stated so you can fail it

Extract the interface when one of these is true, and not otherwise:

1. **A second implementation exists now.** Not imagined. In your editor, or in the tests.
2. **The requirements name one.** `corpus/parking-lot/curveballs/01-motorbikes-share-a-spot` is a
   second packing rule, stated as a requirement. `SpotAllocator` was already the seam it needed, so
   the patch absorbs it in `reference_diff: 2`: one new `SharedMotorbikeAllocator.java`, plus one
   rewritten constructor line.
3. **The interviewer has said that axis moves.** "Pricing changes often" is a requirement, spoken
   aloud. Write it down and treat it as one.

Everything else stays a private method. `contrast/` measures what it costs to change your mind
later: 32 lines in one file, base suite green the whole way. Nineteen of those 32 are lines that
moved rather than lines rewritten.

That number is also the sentence to say out loud when an interviewer asks why the tariff is still a
method. *"One tariff today, so it is a private method. If a second arrives I pull out a
`PricingPolicy`. That is a one-file refactor with the suite green, and I would rather pay it once I
know the shape of the second tariff."*

That answer scores on D3 and on F4. Inventing the interface up front scores on neither.
