Importance of SOLID Design Principles

SOLID Design principles | what | Importance | Usage


SOLID is a popular set of design principles used in the development of object-oriented software.SOLID is an acronym that stands for five key design principles: single responsibility, open-closed, Liskov substitution, interface segregation, and dependency inversion.

     All five are widely used by software engineers and offer significant advantages to developers. There are numerous design guidelines, patterns, and principles in the world of object-oriented programming (OOP). Five of these principles are commonly grouped together and are abbreviated as SOLID. While each of these five principles describes a distinct concept, they also overlap to the point where adopting one implies or leads to the adoption of another.

 SOLID-compliant programming is not usually expected. However, programmers must be aware of SOLID and use it appropriately. 

In general, SOLID assists us in managing code complexity. It results in code that is more maintainable and extensible. Even with large change requests, updating the code is simpler.


   1. Single Responsibility Principle

Example for more clarification 



    


Creating an Employee.java class


  public class Employee{
  private String employeeId;
  private String name;
  private string address; 
  private Date dateOfJoining;
  public boolean isPromotionDueThisYear(){
    //promotion logic implementation
  }
  public Double calcIncomeTaxForCurrentYear(){
    //income tax logic implementation
  }
  //Getters & Setters for all the private attributes
}

It appears logically correct in this above Employee class because it contains all of the employee attributes such as employeeId, name, age, address, and dateOfJoining.

It also calculates the income tax that the employee must pay for the year and tells you if the employee is eligible for promotion for the current year.


Using the Principle of Single Responsibility Let's take a look at how the Employee class breaks down -


The reasoning of determining whether an employee is due this year is not one that the employee is responsible for. Based on the company's HR policy, which may change every few years, the HR department has this obligation. Any changes in HR regulations will necessitate updating the Employee class, which is currently in charge of promotion decisions.

    Similarly, the employee is not responsible for calculating income taxes. It is the responsibility of the financial department to maintain the present tax structure, which may be amended every year. If the Employee class is in charge of income tax computations, the Employee class will need to be updated if the tax structure or calculations change. Lastly, Employee class should have the single responsibility of maintaining the core attributes of an employee.


Refactoring the Employee class so that it adheres to Single Responsibility Principle

Creating a class called HRPromotions.java and Lets move the promotion determination logic from

Employee class to the HRPromotions class like this –


public class HRPromotions{
  public boolean isPromotionDueThisYear(Employee emp){
    //promotion logic implementation using the employee information passed
  }

Similarly, lets move the income tax calculation logic from Employee class to FinITCalculations class


public class FinITCalculations{
  public Double calcIncomeTaxForCurrentYear(Employee emp){
    //income tax logic implementation using the employee information passed
  }
}

Our Employee class now remains with a single responsibility of maintaining core employee attributes


public class Employee{ 
  private String employeeId;
  private String name;
  private string address; 
  private Date dateOfJoining;
  //Getters & Setters for all the private attributes
}


2. Open - Close Principle


Software entities (e.g., classes, modules,functions) should be open

for an extension, but closed for modification.


Consider the following method from the VehicleCalculations class:


public class VehicleCalculations {
    public double calculateValue(Vehicle v) {
        if (v instanceof Car) {
            return v.getValue() * 0.8;
        if (v instanceof Bike) {
            return v.getValue() * 0.5;

    }
}

Let's say we want to create a new subclass called Truck. We'd have to add another if expression to the

aforementioned class, which goes against the Open-Closed Principle. A better solution would be for the

Car and Truck subclasses to override the calculateValue method:


public class Vehicle {
    public double calculateValue() {...}
}
public class Car extends Vehicle {
    public double calculateValue() {
        return this.getValue() * 0.8;
}
public class Truck extends Vehicle{
    public double calculateValue() {
        return this.getValue() * 0.9;
}

Making a new subclass and extending from the Vehicle class is all it takes to add a new Vehicle type.

3. Liskov substitution principle


The Liskov Substitution Principle (LSP) states that derived

classes must be totally replaceable with their base classes in

inheritance hierarchies.

Consider the following example of a Rectangle base class and a Square derived 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);
    }
}



Because you can't substitute the Rectangle base class with its derived class Square,the above classes

don't follow LSP. The Square class includes additional requirements, such as the height and breadth

must be equal. As a result, swapping Square for Rectangle may result in unexpected behavior.

4. Interface segregation principle

Clients should not be forced to rely on interface members they do

not use, according to the Interface Segregation Principle (ISP).

To put it another way, don't make any client implement an

interface that they don't need.

Assume there's a vehicle interface and a Bike class:


public interface Vehicle {
    public void drive();
    public void stop();
    public void refuel();
    public void openDoors();
}
public class Bike implements Vehicle {

    // Can be implemented
    public void drive() {...}
    public void stop() {...}
    public void refuel() {...}
    
    // Can not be implemented
    public void openDoors() {...}
}

As you can see, a Bike class should not implement the openDoors() method because a bike does not

have any doors! To address this, ISP offers breaking down the interfaces into several, small coherent

interfaces so that no class is required to implement any interfaces (and thus methods) that it does not

require.

5. Dependency inversion principle

              

      The Dependency Inversion Principle (DIP) asserts that

 rather than real implementations, we should rely on

 abstractions (interfaces and abstract classes) (classes).


 Details should not be dependent on abstractions; rather,

 abstractions should be dependent on details.                                                                                 



Consider the following scenario. Because the Car class is dependent on the concrete Engine class, it does not follow DIP.


public class Car {
    private Engine engine;
    public Car(Engine e) {
        engine = e;
    }
    public void start() {
        engine.start();
    }
}
public class Engine {
   public void start() {...}
}

For the time being, the code will work, but what if we want to add another engine type, such as a diesel

engine? The Car class will need to be refactored in order to accomplish this.

However, by creating a layer of abstraction, we may fix this problem. Let's add an interface to the

Car instead of relying just on the Engine:


public interface Engine {
    public void start();
}

We can now connect any type of Engine to the Car class that implements the Engine interface:


public class Car {
    private Engine engine;
    public Car(Engine e) {
        engine = e;
    }
    public void start() {
        engine.start();
    }
}
public class PetrolEngine implements Engine {
   public void start() {...}
}
public class DieselEngine implements Engine {
   public void start() {...}
}



Comments

Popular posts from this blog

React JWT Authentication with Axios: Handling Access and Refresh Tokens

What is Sales Force Automation (SFA) and Why Your Business Needs It

Seedr: The Ultimate Torrent Downloader