Why this matters
DIP states that high-level policy (business rules) should not depend on low-level detail (databases, frameworks, I/O). Both should depend on abstractions. In the ideal DIP world, every source-code dependency points toward an abstract interface, never toward a concrete implementation.
Martin illustrates the practical consequence with the Abstract Factory pattern. Business code needs to create objects — an order, a repository, a payment — but it must not directly call new MySQLOrderRepository(), because that would couple stable business policy to a volatile infrastructure choice. The Abstract Factory creates concrete objects on the far side of an architectural boundary, so business code depends only on the factory interface and the repository interface. The "curved line" separating abstract from concrete is the architectural boundary itself — everything stable and reusable on one side, everything volatile and replaceable on the other.
The problem
OrderService directly instantiates MySQLOrderRepository — the business layer is hardwired to a database technology it should never know about.
Bad
class MySQLOrderRepository:
def save(self, order: dict) -> None:
mysql.execute("INSERT INTO orders ...", order)
class OrderService:
def __init__(self):
self.repo = MySQLOrderRepository() # hardwired to MySQL!
def place_order(self, order: dict) -> None:
# business logic...
self.repo.save(order)
class MySQLOrderRepository {
save(order: Order): void {
mysql.execute("INSERT INTO orders ...", order);
}
}
class OrderService {
private repo = new MySQLOrderRepository(); // hardwired — DIP violation
placeOrder(order: Order): void {
// business logic...
this.repo.save(order);
}
}
The solution
OrderService depends only on an abstract repository interface. An Abstract Factory creates the concrete implementation — business policy and infrastructure detail are separated by an architectural boundary.
Good
from abc import ABC, abstractmethod
class OrderRepositoryInterface(ABC):
@abstractmethod
def save(self, order: dict) -> None: ...
class OrderRepositoryFactory(ABC):
@abstractmethod
def create(self) -> OrderRepositoryInterface: ...
# ── Stable policy (business side) ────────────────────────────────────────────
class OrderService:
def __init__(self, factory: OrderRepositoryFactory):
self.repo = factory.create() # depends on abstraction only
def place_order(self, order: dict) -> None:
# business logic...
self.repo.save(order)
# ── Volatile detail (infrastructure side) ────────────────────────────────────
class MySQLOrderRepository(OrderRepositoryInterface):
def save(self, order: dict) -> None:
mysql.execute("INSERT INTO orders ...", order)
class MySQLRepositoryFactory(OrderRepositoryFactory):
def create(self) -> OrderRepositoryInterface:
return MySQLOrderRepository()
interface OrderRepository {
save(order: Order): void;
}
interface OrderRepositoryFactory {
create(): OrderRepository;
}
// ── Stable policy (business side) ────────────────────────────────────────────
class OrderService {
private repo: OrderRepository;
constructor(factory: OrderRepositoryFactory) {
this.repo = factory.create(); // depends on abstraction
}
placeOrder(order: Order): void {
// business logic...
this.repo.save(order);
}
}
// ── Volatile detail (infrastructure side) ────────────────────────────────────
class MySQLOrderRepository implements OrderRepository {
save(order: Order): void {
mysql.execute("INSERT INTO orders ...", order);
}
}
class MySQLRepositoryFactory implements OrderRepositoryFactory {
create(): OrderRepository { return new MySQLOrderRepository(); }
}
Key takeaway
Every new ConcreteClass() inside your business logic is a DIP violation — it hardwires stable policy to a volatile detail. Use a factory or inject the dependency so the business layer never knows what concrete type it's working with.