SOLID Principals in Programming

Mayank SinghMayank Singh
6 min read

SRP: Single Responsibility Principle

A class should have one and only one reason to change. You should be able to define what an application is doing in one single line.

  1. Program Class:

    • The main method initializes a circle, calculates its area, and then prints it.

    • While it calls both the CalculateArea method and the print method, it does not perform the actual calculation or printing itself.

    • It adheres to SRP by coordinating the execution of different tasks rather than implementing them directly.

public class Program{
    public static void main(String [] args){
        Circle circle= new Circle(5);
        CalculateArea(circle);
        new Printer.print(circle);
    }

    public void CalculateArea(Circle circle){
        Console.WriteLine(circle.getArea());
    }
}
  1. Circle Class:

    • The Circle class is responsible for representing a circle and calculating its area.

    • It has attributes related to circles (Radius) and methods (getArea) to perform operations on circles.

    • It adheres to SRP by focusing solely on circle-related operations.

public class Circle{
    public Circle (double radius){
        Radius=radius;
    }

    public double Radius { get; set; }
    public double getArea(){
        return Math.pi * Radius * Radius;
    }
}
  1. Printer Class:

    • The Printer class is responsible for printing circles.

    • It has a method (print) that takes a circle and prints it.

    • It adheres to SRP by focusing solely on printing circles.

internal Class Printer {
    public void print(Circle circle){
        Console.Writeline(circle);
    }
}

OCP: Open Close Principle

Software Entities should be Open for extension but Closed for Modification.

The below Code does not adhere to OCP as it is open for extension as well as modification. Adding a new Shape will require modification of code which isn't a good practice.

public double Area(Object [] shapes){
    double area=0;
    foreach(var shape in shapes){
        if(shape is Rectangle){
            Rectangle rectangle= (Rectangle) shape;
            area+= rectangle.Width * rectangle.Height;
        }
        else{
            Circle circle= (Circle) shape;
            area+=circle.Radius * circle.Radius * Math.PI;
        }
        return area;
}

To fully adhere to OCP, you could consider using polymorphism and abstraction to handle different types of shapes without the need for explicit type checking and conditional branches in the Area method. By utilizing polymorphism and abstraction, the code allows for easy extension by adding new shape types without modifying existing code.

New shapes can be added by simply inheriting from the Shape class and providing their own implementation of the Area() method. This allows for the extension of the Area() method to accommodate new types of shapes without modifying the existing code.

public abstract class Shape {
    public abstract double Area();
}
public class Rectangle: Shape {
    public double Width {get; set; }
    public double Height{get; set; }
    public override double Area(){
        return Width*Height;
    }
}
public class Circle: Shape {
    public double Radius {get; set; }
    public override double Area(){
        return Radius*Radius*Math.PI;
    }
}

Since each shape class provides its own implementation of the Area() method, there's no need for conditional branches or type checking within the Area method. This means that adding a new shape type doesn't require modifying the existing Area method. The method is closed for modification in terms of adding new shapes.

public double Area(Shape [] shapes){
    double area=0;
    foreach(var shape in shapes){
        area+=shape.Area();
    }
    return area;
}

LSP: Liskov Substitution Principle

Subtypes must be substitutable for their base types. Derived or child classes must be substitutable for their base or parent classes.

The methods setWidth and setHeight behave differently in the Rectangle and Square classes:

class Rectangle {
    vois setWidth (double w);
    void setHeight (double h);
    double getHeight();
    double getWidth();
}

class Square{
    void setWidth(double w); //Set both height and width to w
    void setHeight(double h); //Set both height and width to h
    double getHeight();
    double getWidth();
}
void test (Rectangle r){
    r.setWidth(5);
    r.setHeight(4);
    assertEquals( 5 * 4, r.getWidth() * r.getHeight());
}

In the Rectangle class, setWidth and setHeight set the width and height independently.

In the Square class, setWidth and setHeight set both the width and height to the same value because a square's sides are always equal.

This difference in behavior violates the Liskov Substitution Principle. When an object of the Rectangle class is replaced with an object of the Square class, the client code may expect the setWidth and setHeight methods to behave the same way as they do for a rectangle. However, the Square class changes this behavior, leading to unexpected results.

To adhere to the Liskov Substitution Principle, you should ensure that subclasses can be substituted for their base classes without altering the behavior expected by clients.

ISP: Interface Segregation Principle

The dependency if one class to another one should depend on the smallest possible interface. Client should not be forced to implement interfaces they don't use.

Instead of one fat interface many small interfaces are preferred based on groups of methods, each one serving one submodule.

Lets see by an example:

Animal
    void feed(); //abstract
    void groom(); //abstract

Dog implements Animal
    void feed(); //implementation
    void groom(); //implementation

Tiger implements Animal 
    void feed(); //implementation
    void groom(); //Dummy implementation: Just to keep Complier Happy!!

Now groom is something that is applicable to only domestic or pet animals. So keeping it in Animal is not a good practice as all classes which implement animal must have groom method.

So instead of having one fat interface animal having all features we can create another small interface pet having groom feature. So those classes which implement pet only those classes should provide an implementation of groom.

Animal
    void feed(); //abstract

Pet extends Animal 
    void groom();

Dog extends pet
    void feed(); //implementation
    void groom(); //implementation

Tiger extends Animal
    void feed(); //implementation

DIP: Dependency Inversion Principle

Depend upon abstractions (interfaces) not upon concrete classes.

public class Program{
    public static void main(String [] args){
        Circle circle= new Circle(5);
        CalculateArea(circle);
        new Printer.print(circle);
    }

    public void CalculateArea(Circle circle){
        Console.WriteLine(circle.getArea());
    }
}

internal Class Printer {
    public void print(Circle circle){
        Console.Writeline(circle);
    }
}

Class Program is dependent on Printer. Now suppose we have a new printer say printer2 which does not have print method or its signature is different. In that case I have to modify my Program class code. But if I create an interface named IPrinter and define a print method in it. Now any new printer will implement IPrinter interface. Now in this case Program is dependent on IPrinter and not on concrete class Printer. So now I have to modify my code only if there is change in interface not everytime when printer changes.

public class Program{
    public static void main(String [] args){
        Circle circle= new Circle(5);
        CalculateArea(circle);
        IPrinter printer= new Printer();
        printer.Print(circle);
    }

    public void CalculateArea(Circle circle){
        Console.WriteLine(circle.getArea());
    }
}
internal interface IPrinter{
    void Print(IShape circle);
}

internal Class Printer2 : IPrinter{
    public void Print(IShape circle){
        Console.Writeline(circle);
    }
}
11
Subscribe to my newsletter

Read articles from Mayank Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mayank Singh
Mayank Singh