SOLID Principles

In object-oriented programming languages, the classes are the building blocks of any application. If these blocks are not strong, the building (i.e. the application) is going to face a tough time in the future.

Poorly designed applications can lead the team to very difficult situations when the application scope goes up, or the implementation faces certain design issues either in production or maintenance.

On the other hand, a set of well designed and written classes can speed up the coding process, while reducing the tech debt and the number of bugs in comparison.

In this tutorial, We will learn the SOLID principles which are 5 most recommended design principles, that we should keep in mind while writing our classes.

Table Of Contents

What are SOLID Principles
Single Responsibility Principle
Open Closed Principle
Liskov's Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
5 java class design principles
5 java class design principles

Introduction to SOLID Principles

SOLID is the acronym for a set of practices that, when implemented together, makes the code more adaptive to change. Bob Martin and Micah Martin introduced these concepts in their book ‘Agile Principles, Patterns, and Practices’.

The acronym was meant to help us remember these principles easily. These principles also form a vocabulary we can use while discussing with other team members or as a part of technical documentation shared in the community.

SOLID principles form the fundamental guidelines for building object-oriented applications that are robust, extensible, and maintainable.

Single Responsibility Principle

We may come across one of the principles of object-oriented design, Separation of Concerns (SoC), that conveys a similar idea. The name of the SRP says it all:

“One class should have one and only one responsibility”

In other words, we should write, change, and maintain a class only for one purpose. A class is like a container. We can add any amount of data, fields, and methods into it. However, if we try to achieve too much through a single class, soon that class will become bulky. If we follow SRP, the classes will become compact and neat where each class is responsible for a single problem, task, or concern.

For example, if a given class is a model class then it should strictly represent only one actor/entity in the application. This kind of design decision will give us the flexibility to make changes in the class, in future without worrying the impacts of changes in other classes.

Similarly, If we are writing service/manager class then the class should contain only that part of methods and nothing else. The service class should not contain even utility global functions related to the module.

Better to separate the global functions in another globally accessible class. This will help in maintaining the class for that particular purpose, and we can decide the visibility of class to a specific module only.

Example

We can find plenty of classes in all popular Java libraries which follow single responsibility principle. For example, in Log4j2, we have different classes with logging methods, different classes are logging levels and so on.

In our application level code, we define model classes to represent real time entities such as Person, Employee, Account etc. Most of these classes are examples of SRP principle because when we need to change the state of a person, only then we will modify the Person class, and so on.

In the given example, we have two classes Person and Account. Both have single responsibility to store their specific information. If we want to change the state of Person then we do not need to modify the class Account and vice-versa.

public class Person 
{
	private Long personId;
	private String firstName;
	private String lastName;
	private String age;
	private List<Account> accounts;
}
public class Account 
{
	private Long guid;
	private String accountNumber;
	private String accountName;
	private String status;
	private String type;
}

Open Closed Principle

OCP is the second principle which we should keep in mind while designing our application. It states:

“Software components should be open for extension, but closed for modification”

It means that the application classes should be designed in such a way that whenever fellow developers want to change the flow of control in specific conditions in application, all they need to extend the class and override some functions and that’s it.

If other developers are not able to write the desired behavior due to constraints put by the class, then we should reconsider refactoring the class.

I do not mean here that anybody can change the whole logic of the class, but one should be able to override the options provided by software in a non harmful way permitted by the software.

Example

If we take a look into any good framework like struts or spring, we will see that we can not change their core logic and request processing, but we modify the desired application flow just by extending some classes and plugin them in configuration files.

For example, spring framework has class DispatcherServlet. This class acts as a front controller for String based web applications. To use this class, we are not required to modify this class. All we need is to pass initialization parameters and we can extend its functionality the way we want.

Please note that apart from passing initialization parameters during application startup, we can override methods as well to modify the behavior of target class by extending the classes. For example, struts Action classes are extended to override the request processing logic.

public class HelloWorldAction extends Action 
{
	@Override
	public ActionForward execute(ActionMapping mapping, 
			ActionForm form, 
			HttpServletRequest request, 
			HttpServletResponse response) 
			throws Exception 
	{
	    //Process the request
    }
}

Liskov’s Substitution Principle

LSP is a variation of previously discussed open closed principle. It says:

“Derived types must be completely substitutable for their base types”

LSP means that the classes, fellow developers created by extending our class, should be able to fit in application without failure. This is important when we resort to polymorphic behavior through inheritance.

This requires the objects of the subclasses to behave in the same way as the objects of the superclass. This is mostly seen in places where we do runtime type identification and then cast it to appropriate reference type.

Example

An example of LSP can be custom property editors in Spring framework. Spring provides property editors to represent properties in a different way than the object itself e.g. parsing human readable inputs from HTTP request parameters or displaying human readable values of pure java objects in view layer e.g. Currency or URL.

Spring can register one property editor for one data type and it is required to follow the constraint mandated by base class PropertyEditorSupport. So if any class extends PropertyEditorSupport class, then it can be substituted by everywhere the base class is required.

For example, every book has an ISBN number which is always in a fixed display format. You can have separate representations of ISBN in database and UI. For this requirement, we may write property editor in such a way –

import java.beans.PropertyEditorSupport;
import org.springframework.util.StringUtils;
import com.howtodoinjava.app.model.Isbn;
 
public class IsbnEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (StringUtils.hasText(text)) {
            setValue(new Isbn(text.trim()));
        } else {
            setValue(null);
        }
    }
 
    @Override
    public String getAsText() {
        Isbn isbn = (Isbn) getValue();
        if (isbn != null) {
            return isbn.getIsbn();
        } else {
            return "";
        }
    }
}

Interface Segregation Principle

This principle is my favorite one. ISP is applicable to interfaces as a single responsibility principle holds to classes. ISP says:

“Clients should not be forced to implement unnecessary methods which they will not use”

Take an example. Developer Alex created an interface Reportable and added two methods generateExcel() and generatedPdf(). Now client ‘A’ wants to use this interface but he intends to use reports only in PDF format and not in excel. Will he be able to use the functionality easily?

NO. He will have to implement both the methods, out of which one is an extra burden put on him by the designer of the software. Either he will implement another method or leave it blank. This is not a good design.

So what is the solution? Solution is to create two interfaces by breaking the existing one. They should be like PdfReportable and ExcelReportable. This will give the flexibility to users to use only the required functionality only.

Example

The best place to look for IPS examples is Java AWT event handlers for handling GUI events fired from keyboard and mouse. It has different listener classes for each kind of event. We only need to write handlers for events, we wish to handle. Nothing is mandatory.

Some of the listeners are –

  • FocusListener
  • KeyListener
  • MouseMotionListener
  • MouseWheelListener
  • TextListener
  • WindowFocusListener

Anytime, we wish to handle any event, just find out a corresponding listener and implement it.

public class MouseMotionListenerImpl implements MouseMotionListener 
{
	@Override
	public void mouseDragged(MouseEvent e) {
		//handler code
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		//handler code
	}
}

Dependency Inversion Principle

Most of us are already familiar with the words used in principle’s name. DI principle says:

“Depend on abstractions, not on concretions”

In other words. we should design our software in such a way that various modules can be separated from each other using an abstract layer to bind them together.

Example

The classical use of this principle of bean configuration in Spring framework.

In the spring framework, all modules are provided as separate components which can work together by simply injecting dependencies in other modules. This dependency is managed externally in XML files.

These separate components are so well closed in their boundaries that we can use them in other software modules apart from spring with the same ease. This has been achieved by dependency inversion and open closed principles. All modules expose only abstraction which is useful in extending the functionality or plug-in in another module.

These were 5 class design principles, also known as SOLID principles, which makes the best practices to be followed to design our application classes.

Happy Learning !!

Was this post helpful?

Join 7000+ Fellow Programmers

Subscribe to get new post notifications, industry updates, best practices, and much more. Directly into your inbox, for free.

39 thoughts on “SOLID Principles”

  1. I think last one ‘Dependency Inversion Principle’ explanation seems not correct. This one in not related to “Dependency Injection”.
    Based on this idea, Robert C. Martin’s definition of the Dependency Inversion Principle consists of two parts:

    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.

    Reply
  2. I have questions about the principle: Liskov Substitution Principle Example.

    In some cases, I have Class A and Class B. They have some repeat codes, I extract these codes to a base abstract Class C and then Class A and B extend C, then the repeate codes can be reused. I think the method violate the Subsititution Principle because Class A and B can not substituted by C. What do you think and what else I can do to handle these repeat codes.

    Thanks

    Reply
  3. All this concept used to given by those who want to become famous only with Articles and use to confuse others
    There are few thumb rule
    1. Re usability
    2. Upgradibility with Zero or minimal downtime
    3. Scalability
    4. Manageability
    5. Loose coupling
    Keep in mind these thumb rule automatically you will design best

    Reply
      • I think the rules Siddharth Singh mentioned like software designing result. The SOLID principles more like methods or some lessons can lead us to the result.

        Reply
  4. Good Explanation!

    But unlike your other posts this just seems theoretical, it would really help if you can include a problem statement and show the design step by step using these principles. Additionally, you may also show what problems we can face when we do not follow or how do we implement in the wrong way.

    Keep up the good work. This site is awesome!

    Thank you Lokesh.

    Reply
    • Liskov Substitution Principle —> (depends on ) —-> Open for extension but closed for modification — > (depends on) —> Dependency Inversion Principle.

      All these rules are inter-related if you use it wisely, you won’t find any problem.

      Let us take an example:

      1. Suppose a requirement comes where we need to have a Class car, with various operations like calculating top speed, mileage, insurance, taxes.
      2. Now in future, requirement comes where cars can be of luxury and non luxury, so in each method we start adding if and else clause to calculate insurance, taxes, top speed for both types.
      3. Now in future, when requirement for sports cars have also come.

      You think of refactoring the code.

      1. You would separate out Car class as abstract. [Following Dependency Inversion Principle]
      2. Luxury, Non Luxury, Super Cars would extend and override the methods. [Subsequently, Follow Liskov Substitution Principle ]

      You might argue that you could have started refactoring at point 2 of requirement itself, that is true but I wanted to show how difficult it can become, if you don’t refactor as soon as you feel it is not following Open Closed principle.

      Summary : If you follow Dependency Inversion Principle you are making sure Open Closed Principle would work, and if you follow that you are making sure Liskov Substitution Principle works.

      P.S : I am not generalizing, it is just that most of the time everything will fall in place, If you follow like this.

      Reply
  5. Hi Lokesh Gupta,

    This is a good read no doubt! however, shouldn’t we mention UncleBob for S.O.L.I.D.

    Regards,
    Vijay Jayaram

    Reply
  6. “For example, if you take a look into any good framework like struts or spring, you will see that you can change their core logic and request processing, BUT you modify the desired application flow just by extending some classes and plugin them in configuration files.”

    Could not understand what you wanted to convey from first part of the sentence.

    Reply
  7. I agree i need a some examples to understand very well, I little bit confused with some principles.but thank you so much!

    Reply

Leave a Comment

HowToDoInJava

A blog about Java and its related technologies, the best practices, algorithms, interview questions, scripting languages, and Python.