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:
| scenario | expected property |
|---|---|
| fixed 20 C source | returns 20.0 |
| repeated calls | stable when source state is unchanged |
| negative valid temperature | remains 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.