Skip to content

Dependency Inversion Principle (DIP)

Business logic should not depend on details like SMS vendors or databases. Both depend on an interface that the business side owns.

Builds on Interfaces and Dependency injection

Stage 2core2 min read

Violation in the Bad Code

NotificationService is the high-level class. It holds the decision "tell the user something". EmailService and SMSService are low-level details. They know how one delivery method works.

In the BadCode version, NotificationService creates both concrete services itself, with new. That gives the business class four jobs it should not have:

  1. It decides which channels exist.
  2. It knows how to build each channel.
  3. It offers one method per channel, such as notifyByEmail and notifyBySMS.
  4. It calls the concrete sending code.

The dependency points from the business rule down to the delivery detail. So when a detail changes, the business rule may have to change too.

Adding WhatsApp shows the problem. You add a field, create a WhatsAppService, and add another method to NotificationService. That also breaks OCP (the Open-Closed Principle), because every new channel means editing the same class.

Problems with This Design

  • Tight coupling: NotificationService cannot be used without the exact senders it picked.
  • Difficult tests: a test cannot swap email or SMS for a harmless fake, because the service builds them inside its constructor.
  • Hard vendor changes: a new provider can force edits in the business class.
  • Hidden setup: constructors, credentials, and network calls are buried inside the service.
  • Growing API: each channel adds another notifyBy... method.
  • Mixed language: the business code talks in vendor or transport terms instead of just saying "send this notification."

The new keyword is not the problem by itself. Creating simple value objects (like a date or an amount) inside business logic is fine. The smell is business code building volatile infrastructure, such as payment gateways, databases, mailers, or system clocks, which tests need to replace.

The GoodCode version adds a NotificationChannel interface, written in business terms, and passes it in through the constructor. The business class then depends on a promise ("something can send a message"). The concrete senders supply the details.

See GoodCode for the inverted direction and DIP-Notes.md for the complete guide.

In this topic

Before code4 files

BadCode/EmailService.java8 linesGitHub
package SOLID.DIP.BadCode;public class EmailService {    public void sendEmail(String message) {        System.out.println("Sending email: " + message);    }}
BadCode/Main.java15 linesGitHub
package SOLID.DIP.BadCode;/** * Bad: NotificationService is tightly coupled to EmailService and SMSService. * Adding a new channel would require modifying NotificationService. */public class Main {    @SuppressWarnings("unused")    public static void main(String[] args) {        NotificationService service = new NotificationService();        service.notifyByEmail("Your order has been shipped");        service.notifyBySMS("OTP is 1234");    }}
BadCode/NotificationService.java34 linesGitHub
package SOLID.DIP.BadCode;/** * Problem: * The NotificationService is tightly coupled with specific implementations * like EmailService and SMSService. * <p> * If the EmailService or SMSService changes, we must modify this class. * Also, if we add a new service (like WhatsApp), we need to create a new object * and update this class again. * <p> * This violates the Dependency Inversion Principle because the high-level * NotificationService depends directly on low-level modules instead of abstractions. */public class NotificationService {    // Direct dependencies on concrete classes    private EmailService emailService;    private SMSService smsService;    // Constructor initializes specific implementations    public NotificationService() {        this.emailService = new EmailService();        this.smsService = new SMSService();    }    public void notifyByEmail(String msg) {        emailService.sendEmail(msg);    }    public void notifyBySMS(String msg) {        smsService.sendSMS(msg);    }}
BadCode/SMSService.java8 linesGitHub
package SOLID.DIP.BadCode;public class SMSService {    public void sendSMS(String message) {        System.out.println("Sending SMS: " + message);    }}

Done with this topic?

How well do you know it?

PreviousInterface Segregation Principle (ISP)
Practise 7 questions