Program to an interface, not an implementation
Depend on abstractions, not on concrete classes.
A Cat that eats any Food is more flexible than a Cat that eats Sausage, and it costs
you nothing at the call site - a sausage is still food. That is the whole principle: name the
category, depend on the category.
Why it exists
Code that reaches for a concrete class only works in the context that concrete class was born in. Making it work somewhere new usually costs more than expected, and the reason is always the same: tight coupling, dependencies on concrete classes rather than interfaces, hardcoded operations. The logic itself was reusable; its wiring to one specific collaborator was not.
The mechanical version, when you want two classes to collaborate:
- Work out what one object actually needs from the other. Which methods does it call?
- Describe those methods in a new interface or abstract class.
- Make the dependency implement that interface.
- Point the first class at the interface instead of the concrete class.
A company welded to its employees, and the fix
Take a software company simulator. Company.createSoftware() instantiates a Designer, a
Programmer and a Tester and calls each one's specific method. It works, and it is welded
shut - a new employee type means editing the company.
Generalize the three methods into one - doWork() - and the company stops caring who turns
up. Push the creation of employees into an abstract getEmployees() that concrete
companies implement, and it stops caring which employees exist at all.
You have just watched the Factory Method pattern assemble itself out of one principle. Most of the catalog works like this.
Try it yourself: Company above still hardcodes createSoftware() as its one activity.
Sketch what generalizing further - a company that can also runPayroll() or pitchClient()
through the same Employee interface - would need doWork() to become, and whether one
method is still enough.
The cost of overapplying it
The honest cost: the code got more complicated and, on the day you write it, no more capable. An interface with exactly one implementation and no second one anywhere on the horizon is ceremony, not design - the extension point it buys is speculative until a second real implementation shows up to use it. Spend the indirection where you actually expect variation, not everywhere a dependency exists.
How it relates to its neighbours
YAGNI is the timing question this principle needs answered before it applies:
build the interface the day a second concrete implementation is real, not the day you can
imagine one. And in this site's additional
patterns, Dependency Injection is the
mechanical delivery system for this principle - programming to an interface says what a
class should depend on; Dependency Injection is how the concrete instance behind that
interface actually gets into the class's hands, from outside, without the class ever calling
new on it.
Where you'll see it in the pattern catalog
Nearly the entire structural and behavioral half of the catalog is this principle wearing a specific shape: Strategy depends on an algorithm interface, Observer depends on a subscriber interface, Adapter and Decorator both depend on the interface they wrap rather than the concrete class underneath. If a pattern page says "the publisher only ever talks through this interface," this is the principle doing the talking.
Check yourself
What actually makes a piece of code hard to reuse in a new project?