Unit 10 · lesson

A Contract Is Only Useful When Implementations Keep It

An interface compiles when method signatures match. Software design still depends on shared behavioral expectations.

Consider:

interface TemperatureSource {
    double celsius();
}

Implementation A:

class FixedSource implements TemperatureSource {
    private final double value;

    FixedSource(double value) {
        this.value = value;
    }

    public double celsius() {
        return value;
    }
}

Implementation B claims the same interface but returns Fahrenheit while naming the method celsius. The compiler cannot infer the unit mistake from double alone.

The program is type-correct and semantically wrong.

Write behavioral constraints

A stronger contract description says:

celsius()
- returns temperature in degrees Celsius
- must not mutate source configuration
- caller may invoke it repeatedly

Tests can now check parts of this promise.

Contract tests

Create a shared test table for every TemperatureSource implementation:

scenarioexpected property
fixed 20 C sourcereturns 20.0
repeated callsstable when source state is unchanged
negative valid temperatureremains negative, not clamped without requirement

The same behavioral tests should be runnable against multiple implementations.

This idea scales into reusable test suites later.

Interface segregation in plain language

If an interface asks a class to implement operations it cannot meaningfully support, the interface probably represents too many capabilities.

Bad fit:

interface Device {
    void print();
    void fly();
    void scanFingerprint();
}

A printer should not need meaningless fly() code just to be a Device.

Smaller capability interfaces make obligations more coherent.

Failure injection

Implement two Formatter classes with this contract:

interface Formatter {
    String format(String label, int value);
}

Contract:

returns LABEL=value with no extra leading/trailing whitespace

Make one implementation violate the rule by returning LABEL: value or adding spaces.

Use the same expected cases against both. The compiler will accept both; your behavioral evidence should reject the broken one.

Evidence

Provide:

  • interface source;
  • a plain-language behavioral contract;
  • two implementations;
  • at least three shared cases;
  • one deliberately nonconforming implementation state;
  • a sentence explaining why type-checking alone could not detect the defect.

This is the point where "programming to an interface" becomes more than syntax. A stable contract needs observable behavior.