In software engineering, SOLID is an acronym for 5 design principles intended to make software designs more understandable, flexible, robust, and maintainable. Adopting these practices can contribute to avoiding code smells too.
The 5 SOLID principles are:
- S - The single-responsibility principle
- O - The open-closed principle
- L - The Liskov substitution principle
- I - The interface segregation principle
- D - The dependency inversion principle
Although the SOLID principles apply to any programming language, in further section I will be explaining each of them with examples written specifically in JAVA.
Single Responsibility Principle
This principle states that “a class should have only one reason to change” which means every class should have a single responsibility.
public class Vehicle {
public void details() {}
public double price() {}
public void addNewVehicle() {}
}
Here the class has multiple reasons to change because the Vehicle
class has three separate responsibilities: printing details, printing price, and adding a new vehicle to Database.
To achieve the goal of the single responsibility principle, we should implement a separate class that performs a single functionality only.
public class VehicleDetails {
public void details() {}
}
public class CalculateVehiclePrice {
public double price() {}
}
public class AddVehicle {
public void addNewVehicle() {}
}
Open-Closed Principle
This principle states that “software entities (classes etc.) should be open for extension, but closed for modification”. This means without modifying anything in a class, it should be extendable.
Let's understand this principle with an example of a notification service
public class NotificationService{
public void sendNotification(String medium) {
if (medium.equals("email")) {}
}
}
Here, if you want to introduce a new medium other than email, let's say send a notification to a mobile number then you need to modify the source code in NotificationService class.
So to overcome this you need to design your code in such a way that everyone can reuse your feature by extending it and if they need any customization they can extend the class and add their feature on top of it.
You can create a new interface like:
public interface NotificationService {
public void sendNotification(String medium);
}
Email Notification:
public class EmailNotification implements NotificationService {
public void sendNotification(String medium){
// write Logic using for sending email
}
}
Mobile Notification:
public class MobileNotification implements NotificationService {
public void sendNotification(String medium){
// write Logic using for sending notification via mobile
}
}
Liskov Substitution Principle
This principle states that “derived classes must be able to substitute for their base classes”. In other words, if class A is a child of class B, then we should be able to replace B with A without interrupting the current behavior of the program.
Consider an example of a square class derived from Rectangle base class:
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(h);
}
public void setWidth(double w) {
super.setHeight(w);
super.setWidth(w);
}
}
In the Rectangle
class, setting width and height seems perfectly logical. However, in the square class, the SetWidth() and SetHeight() don't make sense because setting one would change the other to match it.
In this case, Square fails the Liskov substitution test 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.
Interface Segregation Principle
This principle applies to Interfaces and it is similar to the single responsibility principle. It states that “ a client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.“.
Let's understand this by the example of a vehicle interface:
public interface Vehicle {
public void drive();
public void stop();
public void refuel();
public void openDoors();
}
Let's say we now create a Bike
class using this Vehicle interface
public class Bike implements Vehicle {
public void drive() {}
public void stop() {}
public void refuel() {}
// Can not be implemented
public void openDoors() {}
}
Since Bike doesn't have doors we can't implement the last function.
To fix this, it is recommended to break down the interfaces into small multiple, interfaces so that no class is forced to implement any interface or methods, that it does not need.
public interface Vehicle {
public void drive();
public void stop();
public void refuel();
}
public interface Doors{
public void openDoors();
}
Creating two classes - Car
and Bike
public class Bike implements Vehicle {
public void drive() {}
public void stop() {}
public void refuel() {}
}
public class Car implements Vehicle, Door {
public void drive() {}
public void stop() {}
public void refuel() {}
public void openDoors() {}
}
Dependency Inversion Principle
The Dependency Inversion Principle (DIP) states that "entities must depend on abstractions (abstract classes and interfaces), and not on concrete implementations (classes). Also, the high-level module must not depend on the low-level module, but both should depend on abstractions".
Suppose there is a book store that enables customers to put their favorite books on a particular shelf.
In order to implement this functionality, we create a Book
class and a Shelf
class. The Book class will allow users to see reviews of each book they store on the shelves. The Shelf class will let them add a book to their shelf. For example,
public class Book {
void seeReviews() {}
}
public class Shelf {
Book book;
void addBook(Book book) {}
}
Everything looks fine, but as the high-level Shelf
class depends on the low-level Book
, the above code violates the Dependency Inversion Principle. This becomes clear when the store asks us to enable customers to add their own reviews to the shelves, too. In order to fulfill the demand, we create a new UserReview
class:
public class UserReview{
void seeReviews() {}
}
Now, we should modify the Shelf class so that it can accept User Reviews, too. However, this would clearly break the Open/Closed Principle too.
The solution is to create an abstraction layer for the lower-level classes (Book and UserReview). We’ll do so by introducing the Product interface, both classes will implement it. For example, the below code demonstrates the concept.
public interface Product {
void seeReviews();
}
public class Book implements Product {
public void seeReviews() {}
}
public class UserReview implements Product {
public void seeReviews() {}
}
Now, the Shelf can reference the Product interface instead of its implementations (Book and UserReview). The refactored code also allows us to later introduce new product types (for instance, Magazine) that customers can put on their shelves, too.
public class Shelf {
Product product;
void addProduct(Product product) {}
void customizeShelf() {}
}
Conclusion
In this article, you were presented with the five principles of the SOLID Code. Adhering to SOLID principles can make your project, extendable, easily modifiable, well tested, with fewer complications.
Other Articles You Might Like
Introduction to Asynchronous Processing and Message Queues - How to handle complex communication between a client and a server using message queues?
Generating unique IDs in a Large scale Distributed environment - A decent way to generate unique IDs across a distributed system that could also be used as the primary keys in the MySQL tables.
Javascript Clean Code Tips & Good Practices -15 tips I follow for writing better Javascript code.
Five++ cool Python snippets that will blow your mind🤯 - Few clever yet useful tricks and tips that will surely make you think hard.
Welcome to the world of "NFTs" - Learn about what are NFTs and Why are they suddenly becoming the next big thing.