SOLID Principles In Java

Posted By : Dhiraj Chauhan | 30-Nov-2022

Java java microservices

Loading...

Introduction

Software development can benefit from object-oriented design principles known as SOLID. SOLID stands for Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle, as well as five additional class-design principles.

Examples:

1. Single Responsibility Principle

In Java, each class should only perform one task. To be exact, there needs to be just one justification for changing classes. Here is an illustration of a Java class that violates the single responsibility concept.

publicclassVehicle{
publicvoidprintDetails(){}
publicdoublecalculateValue(){}
publicvoidaddVehicleToDB(){}
}

The three distinct duties of the Vehicle class are reporting, calculation, and database. We can divide the aforementioned class into three classes with distinct responsibilities.

2. Open-closed principle

Classes, modules, and other software entities should be open for extension but closed for modification.

Take a look at the method below from the VehicleCalculations class:

publicclassVehicleCalculations{
publicdoublecalculateValue(Vehiclev){
if(vinstanceofCar){
returnv.getValue()*0.8;
if(vinstanceofBike){
returnv.getValue()*0.5;
}
}

Let's say we wish to include a new subclass called Truck. The Open-Closed Principle is violated if we edit the above class by adding another if expression.
The calculated value method should be overridden by the subclasses Car and Truck as opposed to the current solution:

publicclassVehicle{
publicdoublecalculateValue(){...}
}
publicclassCarextendsVehicle{
publicdoublecalculateValue(){
returnthis.getValue()*0.8;
}
publicclassTruckextendsVehicle{
publicdoublecalculateValue(){
returnthis.getValue()*0.9;
}

Making a new subclass and extending from the Vehicle class is all that is necessary to add a new type of vehicle.

3. Liskov Substitution Principle

According to the Liskov Substitution Principle (LSP), derived classes in inheritance hierarchies must be fully interchangeable with their corresponding base classes.

Take a look at a common illustration of a Square derived class and Rectangle base class:

publicclassRectangle{
privatedoubleheight;
privatedoublewidth;
publicvoidsetHeight(doubleh){height=h;}
publicvoidsetWidht(doublew){width=w;}
...
}
publicclassSquareextendsRectangle{
publicvoidsetHeight(doubleh){
super.setHeight(h);
super.setWidth(h);
}
publicvoidsetWidth(doublew){
super.setHeight(w);
super.setWidth(w);
}
}

Because the Square base class cannot be substituted for the Rectangle base class, the aforementioned classes do not adhere to LSP. The Square class is subject to additional restrictions, namely that the height and width must match. Therefore, substituting the Square class for a Rectangle may produce unexpected results.

4. Interface Segregation Principle

Clients shouldn't be made to rely on interface members they don't use, according to the Interface Segregation Principle (ISP). To put it another way, don't make any client implement a feature that doesn't apply to them.

Let's say there is a bike class and there is a vehicle interface:

publicinterfaceVehicle{
publicvoiddrive();
publicvoidstop();
publicvoidrefuel();
publicvoidopenDoors();
}
publicclassBikeimplementsVehicle{

//Canbeimplemented
publicvoiddrive(){...}
publicvoidstop(){...}
publicvoidrefuel(){...}

//Cannotbeimplemented
publicvoidopenDoors(){...}
}

As you can see, since a bike doesn't have any doors, it makes no sense for the Bike class to implement the openDoors() method! ISP suggests splitting up the interfaces into a number of smaller, cohesive interfaces to address this issue and prevent classes from being forced to implement interfaces and related methods that they do not require.

5. Dependency Inversion Principle

According to the Dependency Inversion Principle (DIP), we ought to rely on abstractions (such as interfaces and abstract classes) rather than actual implementations (classes). The details should depend on the abstractions rather than the other way around.

A good example is provided below. Because the concrete Engine class is a dependency of the Car class, it does not follow DIP.

publicclassCar{
privateEngineengine;
publicCar(Enginee){
engine=e;
}
publicvoidstart(){
engine.start();
}
}
publicclassEngine{
publicvoidstart(){...}
}

While the code is now functional, what would happen if we wanted to add another engine type, say a diesel engine? Refactoring the Car class will be necessary.
However, by adding an abstraction layer, we can resolve this. Let's introduce an interface so that the car doesn't rely just on the engine:

publicinterfaceEngine{
publicvoidstart();
}

The Car class can now be connected to any type of Engine that implements the Engine interface:

publicclassCar{
privateEngineengine;
publicCar(Enginee){
engine=e;
}
publicvoidstart(){
engine.start();
}
}
publicclassPetrolEngineimplementsEngine{
publicvoidstart(){...}
}
publicclassDieselEngineimplementsEngine{
publicvoidstart(){...}
}

We provide end-to-end custom ERP development services to solve complex problems associated with business processes. Our development team uses the latest tech stack and open-source software platforms like ERPNext, Odoo, and OptaPlanner to build scalable business solutions at cost-effective rates.