S.O.L.I.D represents five principles of Java which are:
S : is for Single Responsibility Principle:- a class or module should do one thing only. O: is for Open/Closed Principle:-code entities should be open for extension, but closed for modification. L: is for Liskov Substitution Principle:-any child type of a parent type should be able to stand in for that parent without things blowing up. I :is for Interface Segregation Principle:- you should favor many, smaller, client-specific interfaces over one larger. D is for Dependency Inversion:- encourages you to write code that depends upon abstractions rather than upon concrete details.
1. Single Responsibility Principle (SRP): A class or module should do one thing only. According to the single responsibility principle, there should be only one reason due to which a class has to be changed. It means that a class should have one task to do. Imagine designing classes with more than one responsibility/implementing more than one functionality. There’s no one stopping you to do this. But imagine the amount of dependency your class can create within itself in the due course of the development time. So when you are asked to change a certain functionality, you are not really sure how it would impact the other functionalities implemented in the class. The change might or might not impact other features, but you really can’t take risk, especially in production applications. So you end up testing all the dependent features.Suppose you are asked to implement a UserSetting service where in the user can change the settings but before that the user has to be authenticated. One way to implement this would be:
public class UserSettingService {
public void changeEmail(User user) {
if(checkAccess(user)) {
//Grant option to change
}
}
public boolean checkAccess(User user) {
//Verify if the user is valid.
}
}
All looks good, until you would want to reuse the checkAccess code at some other place. One way to correct this is to decompose the UserSettingService into UserSettingService and SecurityService. And move the checkAccess code into SecurityService.
public class UserSettingService {
public void changeEmail(User user) {
if(SecurityService.checkAccess(user)) {
//Grant option to change
}
}
}
public class SecurityService {
public static boolean checkAccess(User user) {
//check the access.
}
}
Another example would be:
Suppose there is a requirement to download the file – may be in csv/json/xml format, parse the file and then update the contents into a database or file system. One approach would be to:
public class Task {
public void downloadFile(location) {
//Download the file
}
public void parseTheFile(file) {
//Parse the contents of the file- XML/JSON/CSV
}
public void persistTheData(data) {
//Persist the data to Database or file system.
}
}
One way to decompose the Task class is to create different classes for downloading the file – Downloader, for parsing the file – Parser and for persisting to the database or file system.
2. Open Closed Principle: Code entities should be open for extension, but closed for modification. Ways of extending the class include:
– Inheriting from the class
– Overwriting the required behaviors from the class
– Extending certain behaviors of the class
An excellent example of open closed principle can be understood with the help of browsers.(Extensions in chrome browser). Basic function of chrome browser is to surf different sites. Do you want to check grammar when you are writing an email using chrome browser?If yes, you can simply use Grammarly extension, it provides you grammar check on the content.
This mechanism where you are adding things for increasing the functionality of the browser is an extension. Hence, the browser is a perfect example of functionality that is open for extension but is closed for modification. In simple words, you can enhance the functionality by adding/installing plugins on your browser, but cannot build anything new. Let’s take another example.
You are using any Spring function functionality. You can obviously can not change core logic of it but you can extend Spring framework classes and create your own one.
3. Liskov Substitution Principle: Any child type of a parent type should be able to stand in for that parent. Every subclass or derived class should be substitutable for their parent or base class. The principle defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. That requires the objects of your subclasses to behave in the same way as the objects of your superclass. Example: Covariant return type allowed in method overriding. i.e Let assume we have base class Parent and derived class Child, and both have one common method getInstance. Now super class is returning Parent class while derived class is returning Child class. This is Covariant return type.
4. Interface Segregation Principle: You should favor many, smaller, client-specific interfaces over one larger. According to interface segregation principle, a client, no matter what should never be forced to implement an interface that it does not use or the client should never be obliged to depend on any method, which is not used by them. Prefer the interfaces, which are small but client specific instead of monolithic and bigger interface. Let’s take a simple example. You are implementing your own ArrayList and LinkedList in java. You create an interface called List which both classes will implement.
public interface List {
public T get();
public void add(T t);
public T poll();
public T peek();
}
public class LinkedList implements List{
@Override
public Integer get() {
// Implement this method
return null;
}
@Override
public void add(Integer t) {
// Implement this method
}
@Override
public Integer poll() {
// Implement this method
return null;
}
@Override
public Integer peek() {
// Implement this method
return null;
}
}
public class ArrayList implements List{
@Override
public Integer get() {
// Implement this method
return null;
}
@Override
public void add(Integer t) {
// Implement this method
}
@Override
public Integer poll() {
// ArrayList does not require this method
return null;
}
@Override
public Integer peek() {
// ArrayList does not require this method
return null;
}
}
Do you see the problem, even though you do not require poll and peek method in ArrayList, we have implemented them.
The correct solution for above problem will be:
Create another interface called Deque which will have peek and poll method.
public interface Deque {
public T poll();
public T peek();
}
And remove peek and poll from list interface.
public interface List {
public T get();
public void add(T t);
}
Let’s change LinkedList class now.
public class LinkedList implements List,Deque{
@Override
public Integer get() {
// Implement this method
return null;
}
@Override
public void add(Integer t) {
// Implement this method
}
@Override
public Integer poll() {
// Implement this method
return null;
}
@Override
public Integer peek() {
// Implement this method
return null;
}
}
Let’s change ArrayList class now.
public class ArrayList implements List{
@Override
public Integer get() {
// Implement this method
return null;
}
@Override
public void add(Integer t) {
// Implement this method
}
}
5. Dependency Inversion Principle: Encourages you to write code that depends upon abstractions rather than upon concrete details. Let us again understand it through another practical example.
You go to a local store to buy something, and you decide to pay for it by using your debit card. So, when you give your card to the clerk for making the payment, the clerk doesn’t bother to check what kind of card you have given. Even if you have given a Visa card, he will not put out a Visa machine for swiping your card. The type of credit card or debit card that you have for paying does not even matter; they will simply swipe it. So, in this example, you can see that both you and the clerk are dependent on the credit card abstraction and you are not worried about the specifics of the card. This is what a dependency inversion principle is.
