Dependency Inversion
Definition

Violation Example
// BAD: High-level depends directly on low-level
public class OrderService {
// Direct dependency on concrete implementation
private MySQLDatabase database = new MySQLDatabase();
private SmtpEmailSender emailSender = new SmtpEmailSender();
private StripePayment payment = new StripePayment();
public void createOrder(Order order) {
// Tightly coupled to MySQL
database.query("INSERT INTO orders...");
// Tightly coupled to Stripe
payment.charge(order.getTotal());
// Tightly coupled to SMTP
emailSender.send(order.getCustomerEmail(), "Order confirmed");
}
}
// Problems:
// - Can't test without real MySQL, Stripe, SMTP
// - Can't switch to PostgreSQL without changing OrderService
// - Can't switch to SendGrid without changing OrderService
// - High-level business logic depends on low-level details
Proper Implementation
// GOOD: Both depend on abstractions
// Abstractions (interfaces owned by high-level module)
public interface OrderRepository {
void save(Order order);
Optional<Order> findById(OrderId id);
}
public interface PaymentGateway {
PaymentResult charge(Money amount, PaymentDetails details);
}
public interface NotificationService {
void sendOrderConfirmation(Order order);
}
// High-level module depends on abstractions
public class OrderService {
private final OrderRepository orderRepo;
private final PaymentGateway payment;
private final NotificationService notifications;
// Dependencies injected
public OrderService(OrderRepository orderRepo,
PaymentGateway payment,
NotificationService notifications) {
this.orderRepo = orderRepo;
this.payment = payment;
this.notifications = notifications;
}
public void createOrder(Order order) {
PaymentResult result = payment.charge(
order.getTotal(),
order.getPaymentDetails()
);
if (result.isSuccessful()) {
orderRepo.save(order);
notifications.sendOrderConfirmation(order);
}
}
}
// Low-level modules implement abstractions
public class MySQLOrderRepository implements OrderRepository {
public void save(Order order) { /* MySQL specific */ }
public Optional<Order> findById(OrderId id) { /* MySQL specific */ }
}
public class StripePaymentGateway implements PaymentGateway {
public PaymentResult charge(Money amount, PaymentDetails details) {
// Stripe API calls
}
}
public class EmailNotificationService implements NotificationService {
public void sendOrderConfirmation(Order order) {
// SMTP/SendGrid implementation
}
}
Interface Ownership

Dependency Injection
// Dependency Injection is the mechanism to achieve DIP
// 1. CONSTRUCTOR INJECTION (Preferred)
public class OrderService {
private final OrderRepository repo;
public OrderService(OrderRepository repo) {
this.repo = repo;
}
}
// 2. SETTER INJECTION
public class OrderService {
private OrderRepository repo;
public void setRepository(OrderRepository repo) {
this.repo = repo;
}
}
// 3. INTERFACE INJECTION
public interface RepositoryAware {
void setRepository(OrderRepository repo);
}
// WIRING (using Spring)
@Configuration
public class AppConfig {
@Bean
public OrderRepository orderRepository() {
return new MySQLOrderRepository(dataSource());
}
@Bean
public OrderService orderService() {
return new OrderService(orderRepository());
}
}
// OR using annotations
@Service
public class OrderService {
private final OrderRepository repo;
@Autowired // Spring injects the implementation
public OrderService(OrderRepository repo) {
this.repo = repo;
}
}
Testing Benefits
// DIP enables easy testing with mocks
public class OrderServiceTest {
@Test
void shouldSaveOrderAfterSuccessfulPayment() {
// Arrange - use test doubles
OrderRepository mockRepo = mock(OrderRepository.class);
PaymentGateway mockPayment = mock(PaymentGateway.class);
NotificationService mockNotify = mock(NotificationService.class);
when(mockPayment.charge(any(), any()))
.thenReturn(PaymentResult.success());
OrderService service = new OrderService(
mockRepo, mockPayment, mockNotify
);
// Act
Order order = new Order(/* ... */);
service.createOrder(order);
// Assert
verify(mockRepo).save(order);
verify(mockNotify).sendOrderConfirmation(order);
}
@Test
void shouldNotSaveOrderWhenPaymentFails() {
// Arrange
OrderRepository mockRepo = mock(OrderRepository.class);
PaymentGateway mockPayment = mock(PaymentGateway.class);
when(mockPayment.charge(any(), any()))
.thenReturn(PaymentResult.failed("Declined"));
OrderService service = new OrderService(
mockRepo, mockPayment, mock(NotificationService.class)
);
// Act
service.createOrder(new Order(/* ... */));
// Assert - order not saved
verify(mockRepo, never()).save(any());
}
}
Architectural Application

Tips & Tricks
