Dependency Inversion
OrderService creates MySqlDatabase inside itself with new.
Let both sides depend on a small interface.
A business class creates its own helpers with new
The picture from life
A lamp does not know which power station makes the electricity. It depends on the wall socket, a standard shape. The power company can change its plant, solar or coal, and the lamp never notices, because both sides depend on the socket and not on each other.
The problem
What goes wrong when you do not use it.
OrderService creates MySqlDatabase inside itself with new. You cannot test it without a real database, and switching to another store means editing OrderService. The important business class is chained to a low-level detail.
- calls / uses
- creates
- A business class creates its own helpers with new
- You cannot unit-test without a real database, network or clock
- Swapping a vendor means editing core logic
The idea
How the pattern fixes it.
Let both sides depend on a small interface. OrderService asks for "something that can save" and receives it from outside (dependency injection). Production passes MySQL, tests pass an in-memory fake.
- implements
- calls / uses
Who is who
- Store (interface)
- The socket: save(order).
- MySqlStore, MemoryStore
- Real and fake implementations of the socket.
- OrderService
- High-level logic. Receives a Store, never creates one.
Build it in steps
- 1Find the class that does new on a helper.
- 2Extract a small interface for what it needs.
- 3Accept that interface in the constructor.
- 4Wire the real one in one place at startup.
Watch it run
Step through the calls. Tap an arrow to jump to it.
The test hands in a fake store. No database is needed.
OrderService(MemoryStore())The code
Python and Java. Try writing the classes yourself from the diagram first.
class Store:
def save(self, order):
raise NotImplementedError
class MemoryStore(Store):
def __init__(self):
self.orders = []
def save(self, order):
self.orders.append(order)
class OrderService:
def __init__(self, store): # injected from outside
self.store = store
def place(self, order):
self.store.save(order)
return "placed " + order
def demo():
store = MemoryStore()
message = OrderService(store).place("tea")
return message, store.ordersWhat you gain
- Easy to test with fakes
- Swap implementations without touching core logic
What it costs
- More indirection to read
- Needs a place that wires things together
You have already met it in
- Spring and other dependency-injection containers
- Passing a Clock or a Logger into a class instead of calling globals
Often mixed up with
- Factory MethodA factory creates objects for you; DIP says do not create them inside the high-level class at all.
Make it stick
Write a ReminderService that sends messages. Inject a Sender so you can test it with a fake that records messages instead of sending them.