Interfaces

An interface is the surface through which a software component exposes its capabilities to the rest of a system. It is the boundary a caller depends on, separating the what, the operations a component offers and the obligations they carry, from the how, the implementation that fulfills them. An interface is the public side of an abstraction, and the implementation behind it is free to change as long as the interface still holds.

An interface is more than a list of operation signatures. It also carries a contract: the preconditions each operation expects, the postconditions and return values it guarantees, and the invariants the component maintains across calls. A caller that respects the preconditions is entitled to rely on the postconditions. This behavioral promise is what lets a caller reason about a component without reading its implementation. When the contract is left implicit, callers come to depend on incidental behavior the implementer never meant to promise, and a change that should have been safe breaks them.

The mechanism a language provides for declaring an interface varies. In object-oriented programming languages an interface is often a first-class type. Java, C#, and TypeScript have the interface keyword, while Swift and Rust expose protocols and traits. An abstract class can serve the same role where a language offers no separate interface construct. Dynamically typed languages have no syntactic interface at all. The interface is implicit in the set of methods a caller uses, a style known as duck typing. In every case the underlying idea is the same. The caller names the operations it relies on, and any object that provides them can stand in.

At larger scales the same idea recurs. A module or package has a public interface, its exported functions and types, distinct from its internal helpers. A service exposes an interface over a network, whether a REST API, an RPC endpoint, or a message schema. Where the contract has to be machine-checkable and language-neutral, an interface definition language (IDL) describes it in a form tooling can generate code from. The unit changes, but the principle, fixing the contract while varying the implementation, does not.

The value of an interface is that it lets implementations vary independently of their callers. A caller coded against an interface can be given any implementation that satisfies it, which is the basis of polymorphism and of decoupling between modules. The same boundary keeps coupling weak and localized rather than diffused through the implementation. It is also what makes a component testable in isolation, since test-driven development can substitute a fake for a real collaborator through the interface. The same substitution lets new behavior be added by supplying a new implementation rather than editing every caller, which is how interfaces support extensibility and patterns such as the adapter that translate between incompatible interfaces.

Designing an interface is a judgment about its callers. An interface shaped by what a particular client needs stays narrow and cohesive, where one that mirrors everything an implementer happens to provide grows fat and couples every caller to the full surface. That judgment is the subject of the interface segregation principle. The other half of the discipline is keeping the interface stable once published, so the implementation can evolve without forcing every caller to change with it. This is the concern of backwards compatibility and of dependency inversion, which asks both sides of a dependency to depend on the abstraction rather than on a concrete implementation. Encapsulation is what enforces the boundary, ensuring that changes behind the interface do not leak out through shared state or exposed internals.

Important

An interface promises only what its contract states. Behavior a caller relies on that the contract never granted, whether a side effect, an ordering, or a performance characteristic, is a leaky abstraction in the making, and the implementer is free to withdraw it without warning.

See also

References

  • Meyer, Bertrand (1997). Object-Oriented Software Construction (2nd ed.). Prentice Hall.