SOLID is a set of five design principles used in object-oriented programming to make software easier to understand, flexible and maintain.
Robert C. Martin introduced the theory of SOLID in year 2000 on his paper Design Principles and Design Patterns. Later, Micheal Feathers introduced the SOLID acronym.
SOLID stands for
- Single-responsibility Principle
- Open-closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Following writing will discussed on one of the principles which is Dependency Inversion Principle (DIP).
Table of contents
Concept
Depend upon abstractions, [not] concretions
-Robert C. Martin
The general idea of DIP is that when designing between high-level modules and low-level modules, the interaction between them should be design as abstract. Meaning for high-level modules which provide complex logic, low-level modules which provide utility functions can easily be reuse and modify.
Thus, from the idea, DIP stated:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
Therefore, it dictates that both high-level modules and low-level modules must depend on same abstractions. It splits the dependency between high-level modules and low-level modules by introduce an abstraction between them.
Example
Consider the following implementation:
public class SecurityManager {
private final UserServicePg userService;
public SecurityManager(UserServicePg userService) {
this.userService = userService;
}
public List<User> getUserList() {
return userService.getUserList();
}
}
public class UserServicePg {
public List<User> getUserList() {
return new ArrayList<>(); // Get results from Postgres
}
}
From above example, the SecurityManager
class uses the concrete UserServicePg
class. Therefore, it is tightly coupled in the implementation. It means when the implementation need to change the configuration like database from Postgres to Mongo, the SecurityManager
need to be change.
Thus, DIP can be uses to make SecurityManager
and UserServicePg
class more loosely coupled.
Based on DIP definition, a high-level module should not depend on low-level module. And both should depend on abstraction. From the example, SecurityManager
depends on the UserServicePg
, so SecurityManager
is a high-level module and UserServicePg
is a low-level module.
Next, abstraction need to be define between both high-level module and low-level module. An abstraction in programming means to create an interface or abstract class which allows both high-level module and low-level module to depend on and create interaction between them.
Thus, UserServicePg
class method can be abstract into interface class like below:
public interface IUserService {
public List<User> getUserList();
}
Next, the interface can be implemented into UserServicePg
class.
public class UserServicePg implements IUserService {
@Override
public List<User> getUserList() {
return new ArrayList<>(); // Get results from Postgres
}
}
Now, change the SecurityManager
to accept interface as parameter for dependency.
public class SecurityManager {
private final UserService userService;
public SecurityManager(UserService userService) {
this.userService = userService;
}
public List<User> getUserList() {
return userService.getUserList();
}
}
With the latest implementation, the SecurityManager
class can accept any class which implements the UserService
interface. Thus, if next development required change to the configuration like database, it can simply be change without making any changes to SecurityManager
class which is high-level module.
Conclusion
In conclusion, Dependency Inversion Principle enables abstraction between high-level modules and low-level modules by usage of interface. It allows the dependency between high-level modules and low-leve levels split and both modules will depend on same abstraction to interact.
Thus, it enables developer to change high-level modules and low-level modules without affection any other classes, as long as they do not change any interface abstractions.