Dependency inversion principle
High-level classes should not depend on low-level classes. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Two levels of class show up in almost any system. Low-level classes do the basic mechanical work: reading a disk, pushing bytes over a network, talking to a database. High-level classes hold the business logic that directs the low-level ones.
Formal statement
The principle is two clauses, and both matter. First: high-level modules should not depend
on low-level modules - both should depend on abstractions. Second: abstractions should not
depend on details - details should depend on abstractions. The second clause is the one
people forget. It is not enough to put an interface between BudgetReport and
MySQLDatabase; the interface itself must be phrased in terms the business logic would
invent on its own, not in terms borrowed from the database. An interface with methods named
executeQuery(sql) is still a detail wearing an abstraction's clothes - nothing was
inverted, the coupling was just renamed.
The trouble is the order people build them in. Prototyping a new system, you write the low-level parts first, because until they exist you are not sure what the higher level can even ask for. Perfectly sensible, and it leaves your business logic shaped around the plumbing. Change the plumbing and the business logic changes, which is precisely backwards: a new database server version has no business affecting a budget report.
Inverting it
Three steps:
- Describe what the high level actually needs, in business language. The reporting code
should be able to call
openReport(file), notopenFile(x),readBytes(n),closeFile(x). That vocabulary choice is the load-bearing part - an interface phrased in low-level terms is the low-level class in disguise, and it will leak the moment the implementation changes. - Make the high-level classes depend on those interfaces instead of on concrete low-level classes. The coupling is still there, but it is now to something you control and that changes when your business changes.
- Have the low-level classes implement them. Once they do, they depend on the business layer's abstraction, and the original arrow has been turned around.
Note who owns the interface. It is declared by and for the high level, which is what distinguishes this from simply putting an interface in front of everything. This principle also pairs naturally with open/closed: new low-level implementations plug into existing business logic without touching it.
The example
A budget reporting class reads and persists its data through a MySQL database class. Any change down there - a new server version, a schema convention, a migration to something else entirely - propagates up into a class whose job description says nothing about storage.
Declare a Database interface describing the read and write operations the report needs, in
the report's own terms, and let the report depend on that. Then have the MySQL class
implement it, and add a MongoDB class beside it whenever someone wants one. The reporting
logic never learns which one it got.
A second example
The textbook electrical version of the same idea: a Switch holds a reference to a
LightBulb and calls turnOn()/turnOff() directly. Wiring the same switch to a ceiling
fan means editing Switch to know fans exist, which is backwards - a light switch's job is
flipping something on and off, not knowing what that something is.
Declare a Switchable interface with turnOn()/turnOff(), have Switch depend on that,
and let LightBulb and CeilingFan each implement it. Switch stops caring what is on the
other end of the wire, which is exactly what a switch should never have cared about.
The smell
new ConcreteLowLevelThing() appearing inside a high-level class's own method or
constructor body - new MySQLDatabase() inside BudgetReport, new LightBulb() inside
Switch. Constructing a concrete low-level object is choosing which implementation to use,
and that choice belongs to whoever assembles the system, not to the business logic that is
supposed to be indifferent to it.
Patterns that satisfy it
Dependency Injection is this principle's mechanical enforcement: instead of a class constructing its own dependencies (the smell above), something external hands it an already-built implementation of the interface it declared. Abstract Factory often sits at the seam where those implementations get chosen - one factory produces the MySQL family of objects, another produces the MongoDB family, and the high-level code that consumes them never names either concrete family.
Where it goes wrong
This is the SOLID principle most likely to metastasize into architecture astronautics.
- One implementation, one interface, no plausible second. An interface that exists only to be implemented once is a file tax. If the only reason for it is stubbing in tests, ask whether a purpose-built fake or an in-memory implementation would say more about your intent.
- Interfaces that mirror their implementation method for method. If
Databasehas exactly the methods MySQL happens to expose, including the ones named after MySQL concepts, nothing was inverted. You renamed the coupling. - Inverting inside a single layer. The principle is about boundaries between levels - business logic against infrastructure. Two collaborating business classes at the same altitude usually just need to know each other.
- Frameworks that inject everything. A container wiring three hundred single-use interfaces makes the dependency graph invisible rather than flexible.
Invert at the seams that genuinely move: storage, network, third-party services, the clock. Everywhere else, a direct call is a feature, because you can read it. The honest cost of inverting everywhere is a codebase where following one request means opening a dozen interfaces, each with exactly one implementation, before finding a single line that actually does anything.
Try it yourself: Introduce a Database interface that BudgetReport depends on instead
of MySQLDatabase directly, then rewrite MySQLDatabase to implement it so BudgetReport
no longer names a concrete database class anywhere in its own code.
Check yourself
What exactly gets inverted?