What is the Dependency Inversion Principle (DIP) in the context of SOLID principles?
The Dependency Inversion Principle (DIP) is all about separating high-level architecture from low-level implementation details. Which means any software component should depend on interfaces rather than concrete classes.
Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules.
Both abstract and concrete modules should only depend on abstract modules.
RC Martin

DIP was coined by Robert C. Martin in his 1996 paper “The Dependency Inversion Principle” (published in the C++ Report), DIP encourages us to depend on abstractions (interfaces), not concrete implementations (classes).
Now, this doesn’t mean we avoid all concrete dependencies. For example, in Java you’ll always depend on String
. That’s fine—it’s stable and rarely changes. The real danger lies in depending on volatile, work-in-progress modules that are still evolving. Those are the ones that can break your system.
And remember, DIP doesn’t just apply to classes. It extends to any software component—interfaces, packages, jars, or DLLs. If a module exposes abstractions, you can treat it as an “abstract package” and safely build on top of it.
A package or jar or DLL contains abstract classes can be called abstract package, while a package or jar contains concrete classes can be called concrete package. Following DIP role, concrete and abstract packages should only depend on abstract packages.

The most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions.
Robert C Martin in clean architecture book.
Let’s have an example on DIP
In the below diagram, we have three tightly-coupled layers: AddressControllerImpl, AddressServiceImpl, and AddressRepositoryImpl. The flow is simple — the controller calls the service, the service calls the repository, and finally the repository saves the address.
The problem is that each layer depends directly on a concrete implementation rather than an abstraction. For instance, AddressControllerImpl
depends on AddressServiceImpl
, which in turn depends on AddressRepositoryImpl
. This tight coupling makes the code harder to test, less flexible, and more fragile to changes, since swapping out one component means rewriting the layers above it.

To break the tight coupling, we introduce an interface for each class and separate the code into abstract (interfaces) and concrete (implementations) packages. This ensures that dependencies always point to abstractions, not implementations. With this structure, the Dependency Inversion Principle is preserved at both the class level and the package level, making the design more flexible, testable, and maintainable.

Why is it Called Dependency “Inversion”?
Normally, high-level modules depend on low-level modules. DIP inverts this relationship by introducing an interface (Service) that both high-level and low-level modules depend on.
The Application depends on the Service interface, and the Concrete Implementation also adheres to this interface. The Service Factory resolves the dependency at runtime by providing the appropriate implementation.
Why it is important to follow DIP
- Promotes Flexibility & Scalability
High-level modules depend on abstractions, not concrete classes.
Example: You can introduce a newAddressRepositoryImpl
(e.g., switching from SQL to NoSQL) without touchingAddressService
orAddressController
. - Improves Testability
Abstractions allow easy mocking and stubbing in tests.
Example: ReplaceAddressRepositoryImpl
with a mock repository during unit testing, so you can testAddressServiceImpl
in isolation. - Ensures Decoupling
Business logic is shielded from infrastructure details.
Example:AddressController
focuses on the Submit workflow,AddressService
handles the validation logic, and persistence is delegated to any class implementingAddressRepository
. This separation keeps concerns clean and independent.
Stay Abstract Rulebook
- Don’t inherit from volatile concrete classes
Inheritance is a dependency — use it with caution. - Don’t depend on volatile concrete classes
Always depend on abstractions (interfaces). - Don’t override concrete functions
Concrete functions often drag hidden dependencies. Instead, design them as abstract from the start and provide multiple implementations. - Avoid concrete and volatile references
Never mention the name of a concrete class directly in your code. - Use proper creation patterns
When instantiating a concrete class, prefer Dependency Injection or an Abstract Factory. - Execution flow clarity
The client should depend on the service interface, not directly on its implementation, to perform tasks.
In Dependency Inversion, the client depends on the service interface, not on a concrete implementation. The service implementation then depends on the same interface by implementing it. This way, both the client and the implementation depend on an abstraction, which is the essence of dependency inversion.

What is abstract factory?

The Abstract Factory pattern ensures an application depends only on abstractions, not concrete implementations. In the diagram, the Application stays on the abstraction side of the dependency boundary, knowing only two contracts: ServiceFactory (to create services) and Service (to execute business logic). It never touches the implementations.
- From the Application’s view: it has a ServiceFactory to request services and has a Service to perform work.
- On the other side, concrete classes do the work: ServiceFactoryImpl is a ServiceFactory, ServiceImpl is a Service, and ServiceFactoryImpl has a ServiceImpl to create and wire the service.
All dependencies flow in one direction — from the concrete side to the abstract side. This keeps the Application independent of details, making the system flexible, testable, and resilient to change.
Note: the service factory implementation depending on the service implementation. That is fine because bit is isolated from the system and nobody now will access the service implementation directly and it is refrenced in one concrete component (serviceFactory).
Summary: Dependency Inversion Principle (DIP)
The Dependency Inversion Principle (DIP), introduced by Robert C. Martin, ensures high-level modules depend on abstractions, not concrete implementations. By introducing interfaces and separating code into abstract and concrete packages, systems avoid tight coupling, gain flexibility, and become easier to test.
DIP inverts the usual flow: both high-level and low-level modules depend on abstractions, while concrete classes are resolved at runtime through factories or dependency injection. This makes applications more scalable, maintainable, and resilient to change. The Abstract Factory pattern illustrates this by keeping the Application dependent only on contracts, never on implementation details.