0 XP · 0 scenes

Dependency Inversion

Depend on abstractions, not on concrete classes
The problem

OrderService creates MySqlDatabase inside itself with new.

The idea

Let both sides depend on a small interface.

Reach for it when

A business class creates its own helpers with new

1

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.

2

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.

✗ Before
OrderServicedb = MySqlDatabase()place(order)MySqlDatabasesave(order)Testcreates itneeds real MySQL
  • calls / uses
  • creates
OrderService builds MySqlDatabase itself, so you cannot test it without a real database.
Reach for it when
  • 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
3

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.

✓ After
OrderServicestore: Storeplace(order)«interface»Storesave(order)MySqlStoreMemoryStore← for testsdepends on
  • implements
  • calls / uses
Both sides depend on the Store interface. The real store or a fake one is handed in from outside.

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

  1. 1Find the class that does new on a helper.
  2. 2Extract a small interface for what it needs.
  3. 3Accept that interface in the constructor.
  4. 4Wire the real one in one place at startup.
4

Watch it run

Step through the calls. Tap an arrow to jump to it.

TestOrderServiceMemoryStore1OrderService(MemoryStore())2place('tea')3save('tea')4'placed tea'
Step 1/4

The test hands in a fake store. No database is needed.

OrderService(MemoryStore())
5

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.orders

What 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.
6

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.

Confidence