Dependency Inversion Principle
IntermediateHigh-level modules should not depend on low-level modules; both should depend on abstractions — and abstractions should not depend on details.
Overview
DIP (Robert Martin) inverts the traditional dependency direction. Traditionally, a high-level OrderService depends on a low-level MySqlOrderRepository — changes to MySQL affect OrderService. With DIP, both depend on an OrderRepository interface. The concrete MySqlOrderRepository implements the interface. High-level policy (OrderService) is now insulated from low-level details (MySQL specifics). Dependency Injection is the mechanism that applies DIP at runtime. Spring's IoC container is the industrial-strength DIP enabler — it wires abstractions to implementations based on configuration.
DIP Applied to Repository Layer
Without DIP, OrderService constructs MySqlOrderRepository directly — changing to PostgreSQL requires modifying OrderService. With DIP, OrderService depends on an OrderRepository interface; the concrete implementation is injected.
// ❌ DIP violation: high-level depends on low-level concrete class
public class OrderService {
// Directly coupled to MySQL implementation
private final MySqlOrderRepository repo = new MySqlOrderRepository();
public void placeOrder(Order order) {
repo.save(order); // tied to MySQL — cannot test without DB
}
}
// ✅ DIP applied: both depend on abstraction
// 1. Define the abstraction (interface owned by the HIGH-LEVEL module)
public interface OrderRepository {
void save(Order order);
Optional<Order> findById(String id);
List<Order> findByUserId(String userId);
}
// 2. Low-level module implements the abstraction
public class MySqlOrderRepository implements OrderRepository {
private final JdbcTemplate jdbc;
public MySqlOrderRepository(JdbcTemplate jdbc) { this.jdbc = jdbc; }
@Override public void save(Order order) {
jdbc.update("INSERT INTO orders (id, user_id, total) VALUES (?, ?, ?)",
order.getId(), order.getUserId(), order.getTotal());
}
@Override public Optional<Order> findById(String id) {
return jdbc.query("SELECT * FROM orders WHERE id = ?",
ORDER_MAPPER, id).stream().findFirst();
}
@Override public List<Order> findByUserId(String userId) {
return jdbc.query("SELECT * FROM orders WHERE user_id = ?", ORDER_MAPPER, userId);
}
}
// 3. High-level module depends ONLY on the interface
@Service
public class OrderService {
private final OrderRepository orderRepo; // abstraction, not concrete
private final PaymentGateway payment;
public OrderService(OrderRepository orderRepo, PaymentGateway payment) {
this.orderRepo = orderRepo;
this.payment = payment;
}
public Order placeOrder(Cart cart) {
Order order = Order.from(cart);
payment.charge(order.getUserId(), order.getTotal());
orderRepo.save(order);
return order;
}
}
// 4. Spring wires them (DI applies DIP at runtime)
@Configuration
public class DataConfig {
@Bean
public OrderRepository orderRepository(JdbcTemplate jdbc) {
return new MySqlOrderRepository(jdbc);
// Swap to PostgreSQL: return new PostgresOrderRepository(jdbc);
// Swap to MongoDB: return new MongoOrderRepository(mongoTemplate);
}
}
// 5. In tests: inject an in-memory fake — zero database needed
class OrderServiceTest {
@Test void placeOrder_savesOrder() {
List<Order> savedOrders = new ArrayList<>();
OrderRepository fakeRepo = new OrderRepository() {
@Override public void save(Order o) { savedOrders.add(o); }
@Override public Optional<Order> findById(String id) { return Optional.empty(); }
@Override public List<Order> findByUserId(String uid) { return List.of(); }
};
OrderService sut = new OrderService(fakeRepo, new FakePaymentGateway());
sut.placeOrder(Cart.withItems(List.of(new Item("Book", 499.0))));
assertEquals(1, savedOrders.size());
}
}Key Points to Remember
- 1High-level modules must not depend on low-level modules — both depend on interfaces.
- 2The interface is "owned" by the high-level module — low-level modules implement it.
- 3DIP makes the high-level policy stable and immune to changes in low-level details.
- 4Dependency Injection is the runtime mechanism that wires abstractions to implementations.
- 5Without DIP, switching from MySQL to MongoDB requires modifying every high-level class that directly imports MySQL types.
Interview Questions
Sign in to ask AriaWhat is the difference between Dependency Inversion and Dependency Injection?
Who should "own" the abstraction interface — the high-level or low-level module?
How does Spring's IoC container apply DIP?
How does DIP improve testability?
Ask Aria about Dependency Inversion Principle
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.