The SOLID Principles Java are an object-oriented approach to software applications that has changed the way object-oriented applications are written by ensuring modularity, easy maintainability, easy understanding, easy to debug and refactor etc. The acronyms for SOLID principles are following:
- Single Responsibility Principle (SRP)
- Open-Close Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DSP)
SOLID Principles Java
Let us understand each of the Solid Design Principles Java with the help of a real-time example.
1- Single Responsibility Principle (SRP)
The Single Responsibility Principle states that software entities (classes, modules, and methods) must perform a single functionality. As multiple functionalities in a single class will overload the class and affect the whole class if any modification is required, Let us understand the single responsibility principle with the following example:
In the above TicketBookingService class, it has multiple methods such as booking(), cancel(), printTicket(), getMealInfo(), and sendOTP(), which perform multiple functionalities. If we want to modify any of the methods, it may affect the whole class. For example, in the getMealInfo() method, if we want to add features for veg and non-veg meals, this may affect the implementation of the whole TicketBookingService class.
For the TicketBookingService class to achieve single responsibility, we need to separate the functionalities into different classes.
2- Open-Close Principle (OCP)
The Open-Close Principle states that software entities (classes, modules, and methods) must be open for extension but closed for modification, which means we can easily extend a module without modifying it. Let us understand the open-close principle with the NotificationService class of the previous TicketBookingService example.
As we can see in the sendOTP(String media) method, we are taking one attribute “media”, Suppose currently we have email and mobile-based notification services in our NotificationService class.
public class NotificationService {
public void sendOTP(String media){
if(media.equals("email")) {
//JavaMailSender Implementation
}else if(media.equals("mobile")) {
//Twilio APIs Implementation
}
}
}
This can be solved by making NotificationService an interface implemented by EmailNotificationService, MobileNotificationService, and WhatsAppNotificationService. Using this, our NotificationService class will be open for extension but closed for modification and will not get impacted, and in the future, we can add any other notification feature just by implementing NotificationService and providing the specific implementation of its methods.
3- Liskov Substitution Principle
The Liskov Substitution Principle states that the parent class must be completely substituted with its child class without breaking the behaviour of the application. In other words, the child class object can be used to replace the parent class object without interrupting any of the application features and functionalities.
Note: The child class should extend the capabilities of the parent class; it should not narrow them down.
Let us understand the liskov’s substitution principle with the following example:
Above, we can see SocialMediaService is an abstract class, which means those classes that extend it will need to provide their specific implementation to the methods of the SocialMediaService class.
Instagram and WhatsApp are the child classes of the SocialMediaService class and need to provide implementation to the methods, but WhatsApp does not support feature for publishing posts, which means it does not provide the implementation for the publishPost(post: object) method.
The object of the Instagram class can substitute for the object of SocialMediaService, as it provides all the implementation for all of its methods. But the object of the WhatsApp class cannot substitute the SocialMediaService object, and it fails to follow the Liskov Substitution Principle.
The above problem can be easily resolved by creating two different interfaces in place of the SocialMediaService class: the MessageMediaManager and PostMediaManager interfaces.
In the above picture, we can see Instagram class implements both PostMediaManager and MessageMediaManager interfaces and provide their own implementation while WhatsApp class implements only MessageMediaManager and will provide implementation to its methods only.
4- Interface Segregation Principle (ISP)
The Interface Segregation Principle states that we should not force the client to use methods that they do not want to use. In the interface segregation principle, we split the larger interfaces into smaller ones so that the implementation class will provide implementation to those methods that they need. The Interface Segregation Principle’s goal is similar to the Single Responsibility Principle. Let us understand the interface segregation principle with the following example:
We can see in the preceding example that we have an interface called “Vehicle,” which is implemented by two different classes, “Bike” and “Cycle,” respectively. As we know, cycle does not have an engine, so one should not force the Cycle class to provide an implementation for the turnOnEngine() method, as this will violate the interface segregation principle.
We can resolve this issue by splitting the Vehicle interface into two different interfaces, VehicleEngineManager and VehicleUtilityManager, and the implementing class. Bike can implement both interfaces, and the Cycle class implements only the VehicleUtilityManager interface. Because of this, we will not force the Cycle class to implement methods it does not want and will follow the Interface Segregation Principle.
5- Dependency Inversion Principle
The Dependency Inversion Principle states that high-level software entities should not depend on low-level software entities. i.e., instead of concrete implementation, both should depend on abstractions (abstract classes or interfaces). Let us understand dependency inversion principle with the following example:
As we can see, the Store class has a concrete implementation for the GooglePay class, which means it will accept payment only from GooglePay and not be able to take payment through PhonePe. This will violate the dependency inversion principle, as the Store class is tightly coupled with GooglePay and any other app will not be able to do the transaction.
We can resolve the above problem using abstraction. We create an interface UPIPayment, and it will be implemented by GooglePay, PhonePe, etc. The Store class uses the member variable of type UPIPayment interface, and the constructor of the Store class is used to initialise the UPIPayment type. This way, any application will be able to perform the transaction.
FAQs
What are 5 SOLID principles in Java?
SOLID is an acronym for five different class-diagram principles, which are as follows: Single Responsibility Principle, Open-Close Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle.
What are the advantages of SOLID principles?
When the SOLID principles are used properly, it becomes easier to develop and manage the software application. Following are the advantages of SOLID principles:
- Clean: With the use of SOLID principles, our code base becomes clean and standard.
- Maintainable: SOLID principles make our code base clean, which makes it easier to manage and maintain.
- Easy to Refactor: A clean code base makes it easier to refactor or change the code.
- Independent: SOLID principles make the code loosely coupled by reducing dependencies, which makes the code base independent.
- Testable: SOLID principles make the software application easy to test.
- Reusable: SOLID principles reduce dependencies in software modules, which makes the modules reusable.
Why we need Single Responsibility Principle?
The Single Responsibility Principle helps to write clean and easy-to-test code as it says that a class should have only one single responsibility, which means it should have fewer test cases, which makes it easy to test. Also, single responsibility helps reduce dependency on other classes, which helps maintain a clean code base.
Why we need Open-Close Principle?
The Open-Close Principle says that classes should be open for extension but closed for modification. This is essential because, while building software applications, we may add third-party libraries. In that scenario, our application should be able to extend those classes without being modified by them.