A previous post claimed that it was, " ... inheritance - shared by interface and class alike and certainly no peculiarity of the interface - that offers the means of by-passing this one category of ripple-effect. Here, finally, lies the source of the interface's resistance to ripple-effect." (The italics indicated the Java keyword as opposed to the general interface concept.) That is, despite the wisdom of the adage, "Program to an interface not an implementation," the Java interface is hardly the interface to which the adage exclusively refers: from a ripple-effect point of view a class usually serves just as well but few choose this path because of Java's single-inheritance restriction.
Clearly, however, other forces are at play. Darling of the Java community, the interface dominates Java APIs. Yet it need not have been so. The class could have constituted the API of the vast majority of services. Consider the single-class API, Person
, designed in two flavours, Happy and Sad such that Person
- which clients will see - plays superclass to subclasses HappyPerson
and SadPerson
- which clients will not. Given the mounting hostility towards inheritance implementation over the past decade, designers would have had incentive to throttle at birth any other superclass from which HappyPerson
or SadPerson
might have inherited. Designers might, indeed, have tacitly limited the use of inheritance to API-implementation thereby fertilizing a virulent best practice whose growth would have cast in deep shade Java's single-inheritance constraint.
Still, this patently did not happen. Why?
The answer may be that humans aren't compilers.
In the snippet below Person
, as part of an API, offers its services to a client class whose welcome()
method is insensitive to whether Person
is a class or an interface. The programmer, however, is not.
public void welcome(Person person) { System.out.println("Hello, " + person.getName()); }
Consider both cases. If Person
is an interface then the programmer might see this:
public interface Person { . . . public String getName(); }
If a class, then the programmer might see:
public class Person { . . . public String getName() { return nameDirectory.getNameForIdentifier(identifier); } }
From these two seemingly similar cases emerges a sharp distinction. Computationally, procedural abstraction in both cases completely seals the getName()
implementation from all calling classes: the compiler weaves it fabric from method signatures, calling classes never, "Seeing," the internals of the methods on which they depend. But the programmer does. Whereas the interface's getName()
stands naked, stripped of implementation detail, the class's getName()
exposes something called a nameDirectory
thereby bleeding implementation through the API. It spills not over the suit of an aloof compiler but over the exhausted mind of the programmer who can then hardly help but think: what's that? Where is nameDirectory
in the API? How should it be used? Of course, subclasses of Person
may or may not override this specific method and hence may or may not avail of this nameDirectory
but the damage has already been done.
Such are the distractions evaded by programmers who consume APIs as interfaces only. To the programmer, after all, the class is the norm, its participation in an API an irrelevancy: it is a hole in the ground into which dependencies are tossed only to snag on rocky ledges twenty feet down. Where such dependencies lead to, "Home-grown," classes, well and good, the terrain at least feels navigable, but where they lead through gaping APIs to foreign nameDirectory
contraptions beyond reasonable (source) control, anxiety reigns. The interface, on the other hand, stands as the great dependency terminator, a vast granite cliff-face impervious to mental drill-bit. Offering method signatures alone and uncluttered by implementation, the soothing Person
interface presents not the slightest foothold.
Still, programmers are no fools. They know that, beneath the implementation canopy, any service change might claw its way up and mutilate even a sacred interface signature. Few believe that decoupled systems rise from so vacuous a tactic as ripping from each class an interface through which all inter-class communication may then flow. The best systems remain intelligently decomposed into service-providing entities which merit interfaces based on their stability, potential variety and the degree to which they are published.
The attraction of interfaces persists. They cultivate de-coupling and proper structure in spirit if not actuality. They seemingly distinguish two separate planes of ripple-effect, one the traditional plane of dependencies traced through the programmer's godlike powers of procedural-abstraction violation, the other a plane seen from a compiler's minimalist perspective, emptied of diversion. Whether through engineered cunning or sheer wishful thinking, both planes intersect at the interface.
Programmers rightly praise the interface. If they exaggerate its capacity to insulate against ripple-effect changes they do so from a position which, though not syntactic, seems mostly justified. From a ripple-effect point of view, the class and the interface appear close enough to touch but to the programmer they drift worlds apart.