Contribute to Cleaner Code using SOLID Principles


Today, technology has advanced so much that majority of the software developers are developing software applications, taking shortcuts to meet the listed requirements without adhering to basics. It is adding to clear technical debt. Using SOLID principles, techno leads, program managers, scrum masters can step down easily and suggest inspiring ideas to the teams and thereby can contribute to cleaner code.

S — Single Responsibility Principle:

It states that every object in the system should have single responsibility and all the object code should be focused on achieving this responsibility.

Let’s take the class A which does the following operations.

public class A {
/* Objectives:
1. Open a database connection
2. Fetch data from database
3. Write the data in an external file
*/
}
In the above example Class A is focusing on 3 responsibilities/objectives. If any changes happen to any of the three objectives, the class A would be changed and the implementation of the other 2 objectives also might change. According to Single Responsibility principle, we should create thee separate classes A, B, C for each of the above 3 objectives and the class code should be built focusing on their single responsible objective for the respective classes.

public class A {
/* Objective: Open a database connection */


public class B 
/* Objective: Fetch data from database */
}

public class C 
/* Objective: Write the data in an external file */
}

Benefits: If your code adheres "Single Responsibility Principle", it’s easier to be followed, understood, debugged, removed, and refactored. You can make more courageous changes. And if you break something, you break one thing (or fewer), not an entire system.

O — Open/Closed Principle:

This principle suggests that “classes should be open for extension but closed for modification”. What is means is that if the class A is written by the developer AA, and if the developer BB wants some modification on that then developer BB should be easily do that by extending class A, but not by modifying class A.

Benefits: By following "Open/Closed Principle", changes and usage of objects become cheap in matter of time. Objects can evolve with the possibility of working on them step by step, when needed, and not always feeling like you have to rewrite everything. 

L -Liskov’s Substitution Principle


This principle suggests parent classes should be easily substituted with their child classes without blowing up the application.Let’s take following example to understand this.

Let’s consider an Animal parent class.

public class Animal {
    public void makeNoise() {
        System.out.println("I am making noise");
    }
}

Now let’s consider the Cat and Dog classes which extends Animal.

public class Dog extends Animal {
    @Override
    public void makeNoise() {
        System.out.println("bow wow");
    }
}

public class Cat extends Animal {
    @Override
    public void makeNoise() {
        System.out.println("meow meow");
    }
}

Now, wherever in our code we were using Animal class object we must be able to replace it with the Dog or Cat without exploding our code. What do we mean here is the child class should not implement code such that if it is replaced by the parent class then the application will stop running. For ex. if the following class is replace by Animal then our app will crash.

class DumbDog extends Animal {
    @Override
    public void makeNoise() {
        throw new RuntimeException("I can't make noise");
    }

}

Benefits: By following "Liskov Substitution Principle", It’s about many objects which can be easily replaced by objects of the same nature. 

I — Interface Segregation Principle

This principle state that "Make fine grained interfaces that are client specific". That is, Clients should not be forced to depend upon interfaces that they do not use. Let’s take following example to understand this principle.

Let’s look at the below IShape interface:

interface IShape {
    drawCircle();
    drawSquare();
    drawRectangle();

}

This interface draws squares, circles, rectangles. class Circle, Square or Rectangle implementing the IShape interface must define the methods drawCircle(), drawSquare(),drawRectangle().

class Circle implements IShape {
    drawCircle(){
        //...
    }
    drawSquare(){
        //...
    }
    drawRectangle(){
        //...
    }    
}

class Square implements IShape {
    drawCircle(){
        //...
    }
    drawSquare(){
        //...
    }
    drawRectangle(){
        //...
    }    
}

class Rectangle implements IShape {
    drawCircle(){
        //...
    }
    drawSquare(){
        //...
    }
    drawRectangle(){
        //...
    }    
}

It’s quite funny looking at the code above. class Rectangle implements methods (drawCircle and drawSquare) it has no use of, likewise Square implementing drawCircle, and drawRectangle, and class Circle (drawSquare, drawSquare).

If we add another method to the IShape interface, like drawTriangle(),

interface IShape {
    drawCircle();
    drawSquare();
    drawRectangle();
    drawTriangle();
}
the classes must implement the new method or error will be thrown.

To make our IShape interface conform to the Interface Segregation Principle we segregate the actions to different interfaces:

interface IShape {

    draw();
}

interface ICircle {

    drawCircle();

}

interface ISquare {
    drawSquare();

}

interface IRectangle {     drawRectangle();
}


interface ITriangle {
    drawTriangle();
}
class Circle implements ICircle {
    drawCircle() {
        //...
    }
}
class Square implements ISquare {
    drawSquare() {
        //...
    }
}
class Rectangle implements IRectangle {
    drawRectangle() {
        //...
    }    
}
class Triangle implements ITriangle {
    drawTriangle() {
        //...
    }
}
class CustomShape implements IShape {
   draw(){
      //...
   }
}

This way, the ICircle interface handles only the drawing of circles, ISquare handles the drawing of only squares and IRectangle handles drawing of rectangles and so on.

Benefits: The developer will have a clear way to use exactly what they need, instead of being forced to interact with functionalities they don’t need. By not forcing the developers to handle what they don’t need, they need less code. Less code, fewer problems, closer deadlines.

D — Dependency Inversion Principle

This principle suggest that:
  1. High-level modules should not depend on low-level modules. Both should depend on abstractions. 
  2. Abstractions should not depend on details. Details should depend on abstractions.
Benefits: By sending dependencies from the outside world, you can change them more easily. And you know reasons for change: Libraries get really outdated or discontinued etc. If your code uses a dependency from outside, changing it will be cheaper. Also, you can test your units of code.