SOLID principles

What Are SOLID Principles, and Why Do We Need Them?

SOLID principles

I had a big question in my mind when I heard about SOLID principles. What are they and why are they required? How are they different from OOPs concepts?

Gradually, I understood that SOLID principles makes us understand how to apply the oops concepts properly while writing the code. To make the code base more modular(easy to update and change) and scalable(easy to grow without breaking existing functionality).

Without SOLID principles you could not even realize that you are writing a bad code. These principles act like guards. So It helps to avoid bad habits of writing code. It improves your ability of code review.

While writing the code, check with yourself. Does this code following Design principles? In simple terms design principles are the blue prints that follow all the solid principles. I will explain all the design principles in my upcoming blogs.

Let's Dive Into SOLID Principles.

S is for Single Responsibility Principle(SRP).
A class should have Single Responsibility. For example In a ecommerce application an OrderManagement class should be responsible only for managing order related operations. It should not contain Authentication, Validation and Invoice generation code.

Why we need to follow SRP?
Later, if some kind of business requirement has come then we would need to change our class, and this may introduce more bugs. When a class is responsible for many things then the changes in one part can accidentally break other part of the code. So testing area would also increase.

O is for Open for Extension and Closed for Modification:
Code should be open for extension (easy to add new features) but closed for modification (you shouldn’t have to change existing code to add new features). Lets take Swiggy as an example: we have different types of orders like delivery, Dine in, Take away. In the below code, if you want to add the new orderType, you need to modify the existing class. But which is against the solid principles.

public class Order{

    public static void main(String[] args){
          takeOrder(orderType); 
    }
    void takeOrder(OrderType orderType){
        switch(orderType)
            case "delivery":
                System.out.println("delivery order received");
            case "dineIn":
                System.out.println("dine in order received");
            case "takeAway":
                System.out.println("take away order received");

    }

}

To follow the OCP, Instead of switch statement, we can use an abstraction layer using interfaces and polymorphism.


interface OrderType {
    void takeOrder();
}


class DeliveryOrder implements OrderType {
    @Override
    public void takeOrder() {
        System.out.println("Delivery order received");
    }
}

class DineInOrder implements OrderType {
    @Override
    public void takeOrder() {
        System.out.println("Dine-in order received");
    }
}

class TakeAwayOrder implements OrderType {
    @Override
    public void takeOrder() {
        System.out.println("Take-away order received");
    }
}


public class Order {
    public static void main(String[] args) {
        // Example usage
        OrderType order = new DeliveryOrder();
        order.takeOrder();

        order = new DineInOrder(); 
        order.takeOrder();
    }
}

L is for Liskov Substitution Principle: In very simple term, if you replace the parent or super class with subclass class, the program should work seamlessly without any issues.
For example, in the code I replaced the Parent class OrderType with the subclass DeliveryOrder. Even after this change, the code works perfectly, which means it follows the above principle

public class Order {
    public static void main(String[] args) {
        // Example usage
        OrderType order = new DeliveryOrder();
        order.takeOrder();

        DeliveryOrder order = new DeliveryOrder();// this should also work otherwise it voilating LSP
        order.takeOrder();

        order = new DineInOrder(); 
        order.takeOrder();
    }
}

I is for Interface Segregation Principle:
It says that interface should be small and specific.
Do not add unnecessary methods to the interface which are not required by the implemented class.
For example in the below code:

// A single, large interface that violates ISP
interface OrderType {
    void takeOrder();
    void processPayment(); // Unnecessary for orders. another class which implements payments functionality
}


class DeliveryOrder implements OrderType {
    @Override
    public void takeOrder() {
        System.out.println("Delivery order received");
    }

    @Override
    public void processPayment() { 
        // Irrelevant for DeliveryOrder
        System.out.println("Invalid functionality");
    }
}

class TakeAwayOrder implements OrderType {
    @Override
    public void takeOrder() {
        System.out.println("Take-away order received");
    }

    @Override
    public void processPayment() { 
        System.out.println("Invalid functionality");
    }
}

class DineInOrder implements OrderType {
    @Override
    public void takeOrder() {
        System.out.println("Dine-in order received");
    }

    @Override
    public void processPayment() { 
        System.out.println("Invalid functionality");
    }
}

D is for Dependency Inversion principle:
High level module should not directly depend on low level module. Both should depend on abstractions.
For example in the above code: Order is not directly depends on DeliveryOrder or DineInOrder or TakeAwayOrder. It has an OrderType interface which act as an abstraction layer.

Stay tuned for more on design principles in my next blogs. 😊