In this post, we w'll learn the SOLID Principles with example for each principle and also to learn how to use these principles in our daily basis coding.
SOLID Principles is a coding standard that all developers should follow these principles while developing any software. It was promoted by Robert C Martin and is used across the object-oriented design spectrum. When you apply SOLID principles properly in your software, it makes your code more extendable, logical, and easier to read.
When the developer builds software following a bad design, the code can become inflexible and more brittle. Small changes in the software can result in bugs. For these reasons, we should follow SOLID Principles.
It takes some time to understand, but when you follow these principles, it will improve the code quality and help you to understand the most well designed software.
Let us discuss one by one in detail,
1) Single Responsibility Principle
The Single Responsibility Principle(SRP) says that a class should have only one reason to change, which means every class should have a single responsibility or single job or single purpose.
If single class handles multiple functionalities then in future if we do any modification then it will impact whole class not only the enhanced functionality so need to avoid this.
The below example - Customer has the customer details like name, dateOfBirth, age, address, pinCode, phoneNumber and countryCode. As SRP principle states that class should have single responsibility but here if we do any validations on phoneNumber or address we need to do modify the complete Customer class and also it has different responsibility. So it voilates the Single Responsibility Principle.
The above class voilates the Single Responsibility Principle as we have address and phone detail fields are present in the same class.
So those fields need to seperate into different classes and have to call in the customer class as follow.
The updated customer class is below,
If any enhancement comes in the future like do some validation on phoneNumber or address then we do modification on Phone or Address class so we can not do any modification on Customer class so this is what the SRP principle says(every class has single responsibility).
2) Open/Closed Principle
The open/closed principle(OCP) states that a module or entity should be open for extension but closed for modification. It is one of the famous solid principles and very important in object oriented design principle.
Bertrand Meyer says that "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”.
Open for extension means we need to design the software modules/classes in such a way that the new responsibilities or functionalities should be added easily when new requirements come.
On other hand, Closed for modification means we should not modify the class/module until we find some bugs.
Example of voilation of Open/Closed Principle:--
In the below Payment class, there is paymentType parameter, based on this we have payment processing logic for different payment types. But in future if any enhancement comes for new payment type then we need to modify the Payment class so this voilates OCP principle.
public class Payment {
public void paymentProcessing(int amount, String paymentMode) {
if (paymentMode.equals("Cash")) {
//payment process for cash
} else if (paymentMode.equals("CreditCard")) {
//payment process for creditcard
} else if (paymentMode.equals("debitCard")) {
//payment process for debitCard
}
}
}
Example to followed the Open/Closed Principle:-
In the above example voilates OCP principle, need to follow OCP Principle as follows,
public interface PaymentType {
public void processPayment();
}
class Cash implements PaymentType {
@Override
public void processPayment() {
//add cash processing logic
}
}
class DebitCard implements PaymentType {
@Override
public void processPayment() {
//add debitcard processing logic
}
}
In the above example, created interface PaymentType and implemented classes are Cash and DebitCard so here if in future any enhancement for new paymentType then no need to modify the existing classes/interface instead create new implemented class and implement the required payment process logic.
3) Liskov Substitution Principle(LSP)
Liskov Substitution Principle(LSP) is a third SOLID design principle and this defined by Barbara Liskov. It extends the Open/Closed principle and enables you to replace objects of a parent class with objects of a subclass without breaking the application. This requires all subclasses to behave in the same way as the parent class.
The Principle states that, objects of a superclass shall be replaceable with objects of its subclass without breaking the application.
That requires the objects of your subclasses to behave in the same way as the objects of your superclasses.
Example:-
In the below example, the Square is derived class extends Rectangle class, where we can set the height and width.
public class Rectangle {
private double height;
private double width;
public void setHeight(double h) { height = h; }
public void setWidht(double w) { width = w; }
...
}
public class Square extends Rectangle {
public void setHeight(double h) {
super.setHeight(h);
super.setWidth(w);
}
public void setWidth(double h) {
super.setHeight(h);
super.setWidth(w);
}
}
The above code not follow's LSP because you cannot replace the Rectangle base class with its derived class Square. The Square class has extra constraints, i.e., the height and width must be the same. Therefore, substituting Rectangle with Square class may result in unexpected behavior.
This principle is just an extension of the Open Close Principle and it means that we must make sure that new derived classes are extending the base classes without changing their behavior.
4) Interface Segregation Principle
According to Robert Martin,
The Interface Segregation Interface Principle states that, Clients should not be forced to depend upon interfaces that they do not use.
The main aim of this principle is to break the larger application interfaces into smaller one and each interface serves the single purpose/responsibility, it's similar to single responsiblity principle. In Clients code I mean implemented classes, if any unimplemented methods we need to override those methods also, those are really not needed so this voilates Interface Segregation Principle.
Precise application design and correct abstraction is the key behind the Interface Segregation Principle. Though it'll take more time and effort in the design phase of an application and might increase the code complexity, in the end, we get a flexible code.
Let us see below example,
The HotelOrder has an interface and there are four abstract methods, onlineOrder() , walkInCustomerOrder(), onlinePayment() and cashPayment().
public interface HotelOrder {
public void onlineOrder();
public void walkInCustomerOrder();
public void onlinePayment();
public void cashPayment();
}
There are two implemented classes for HotelOrder interface i.e WalkInCustomerOrder and OnlineCustomerOrder. For WalkInCustomerOrder implementation of onlineOrder() method is not needed but HotelOrder is an interface and we need to implement all abstract methods even those methods do not use. Samething for OnlineCustomerOrder, for online customers implementation class, don't need to implement the abstract method(walkInCustomerOrder()).
public class OnlineCustomerOrder implements HotelOrder {
@Override
public void onlineOrder() {
// code logic for online order
}
@Override
public void walkInCustomerOrder() {
//this method implementation is not needed for
//online customers
throw new UnsupportedOperationException();
}
@Override
public void onlinePayment() {
//write a code for online payment
//like calling payment gateway(payment stuffs)
}
@Override
public void cashPayment() {
//this method is not needed for online order customers
//just throw an exception
throw new UnsupportedOperationException();
}
}
public class WalkInCustomerOrder implements HotelOrder{
@Override
public void onlineOrder() {
//this method implementation is not needed
throw new UnsupportedOperationException();
}
@Override
public void walkInCustomerOrder() {
//logic for walkIn Customer order
}
@Override
public void onlinePayment() {
//this method implementation is not needed
throw new UnsupportedOperationException();
}
@Override
public void cashPayment() {
//logic for cash payments
}
}
The above code violates Interface Segregation Principle, as principle says client should not override methods those they do not use.
Code to follow Interface Segregation Principle:-
Create two interfaces one with WalkIn customer and second with Online Customer, In walkin customer interface define the walkin customer related methods like walkInCustomerOrder() and cashPayment() and for online customer interface define the onlinePayment() and onlineOrder() methods so that it follows the principle and no need to override unnecessary methods in the implemented classes.
public interface OnlineCustomerHotelOrder {
public void onlineOrder();
public void onlinePayment();
}
public interface WalkInCustomerHotelOrder {
public void walkInCustomerOrder();
public void cashPayment();
}
Implemented classes for each order,
public class OnlineCustomerOrder implements OnlineCustomerHotelOrder {
@Override
public void onlineOrder() {
// code logic for online order
}
@Override
public void onlinePayment() {
//write a code for online payment
//like calling payment gateway(payment stuffs)
}
}
public class WalkInCustomerOrder implements WalkInCustomerHotelOrder{
@Override
public void walkInCustomerOrder() {
//logic for walkIn Customer order
}
@Override
public void cashPayment() {
//logic for cash payments
}
}
5) Dependency Inversion Principle
The Dependency Inversion Principle is also one of Solid Principle and it concers the relationship between modules and classes.
According to Robert Martin,
The Dependency Inversion Principle says that,
- 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.
Lets see below example,
The TaxCalculator is main class to calculate the different types of tax like GST, Income Tax, Professional tax and so on. In the below example, I have given two types of taxes GST and Income tax and corresponding implemented classes for each tax types.
public class TaxCalculator {
public static void taxCalculator(String mode) {
if (mode.equals("GST")) {
GSTTaxCalculator.calculate();
} else if (mode.equals("IncomeTax")) {
IncomeTaxCalculator.calculate();
}
}
}
class GSTTaxCalculator {
public static void calculate() {
// logic to calculate GST tax
}
}
class IncomeTaxCalculator {
public static void calculate() {
//logic to calculate IncomeTax
}
}
In the code, in future if we need to implement any new tax type then need to modify the main class so it voilates the Dependency Inversion Principle. The implemented class depends on main module/class so that also voilates the principle.
Need to modify the abstract class not the main class and also implemented class/module depends on abstraction not on main module/class.
Code to follow Dependency Inversion Principle:-
Create an interface Itax and both IncomeTaxCalculator and GSTTaxCalculator implements Itax interface.
public class TaxCalculator {
public static void taxCalculator(Itax itax) {
itax.calculate();
}
}
interface Itax {
public void calculate();
}
class GSTTaxCalculator implements Itax{
public void calculate() {
// logic to calculate GST tax
}
}
class IncomeTaxCalculator implements Itax{
public void calculate() {
//logic to calculate IncomeTax
}
}
If any question or clarification or suggestion put comment in the comment section or send an email to me anilnivargi49@gmail.com
Thank you for visting blog.