Skip to main content

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:

  1. Work out what one object actually needs from the other. Which methods does it call?
  2. Describe those methods in a new interface or abstract class.
  3. Make the dependency implement that interface.
  4. 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.

Company...+ createSoftware()Designer+ designArchitecture()Programmer+ writeCode()Tester+ testSoftware()Designer d = new Designer()d.designArchitecture()Programmer p = new Programmer()p.writeCode()Tester t = new Tester()t.testSoftware()
Before: the company knows every employee type by name.

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.

Company...+ getEmployees()+ createSoftware()«interface»Employee+ doWork()DesignerProgrammerTesteremployees = getEmployees()foreach (Employee e in employees) e.doWork()
After: the company depends on a promise, and subclasses decide who keeps it.

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

Question 1 of 3

What actually makes a piece of code hard to reuse in a new project?