DEV Community

Cover image for SOLID Principles Explained in a Simple Way 🛠️✨ with Real-Life Examples
Md Imran
Md Imran

Posted on

SOLID Principles Explained in a Simple Way 🛠️✨ with Real-Life Examples

In software development, the SOLID principles are essential guidelines that help developers create code that is easy to maintain, extend, and understand. These principles were introduced by Robert C. Martin, also known as Uncle Bob, and they are widely adopted in modern software engineering.

Imagine you're building a house. You want it to be strong, easy to modify, and able to grow if needed. The SOLID principles are like rules for creating a "strong" house in programming.

Alright, let's make the SOLID principles even simpler by blending everyday examples with small, clear code snippets. This way, even if you're a beginner, you’ll get the core idea without any headaches.

S - Single Responsibility Principle (SRP)

Rule: Each class should only do one thing.

Real-World Example:

Think of a librarian in a library. Each librarian is assigned a specific task: one manages book loans, another manages book returns, and yet another handles cataloging. If the book return process changes, only the librarian responsible for book returns needs to adjust their workflow, not the entire staff.

Code Example:

Let’s say we have a Book class. If this class manages book information and saves the book to a file, it’s doing too much. Let’s split it.

Before SRP:

class Book:
def **init**(self, title, author):
self.title = title
self. Author = author

def save_to_file(self):
    with open(f"{self.title}.txt", "w") as file:
        file. Write(f"{self.title} by {self. Author}")
Enter fullscreen mode Exit fullscreen mode

After SRP:

class Book:
def **init**(self, title, author):
self.title = title
self.author = author

class BookSaver:
@staticmethod
def save_to_file(book):
with open(f"{book.title}.txt", "w") as file:
file.write(f"{book.title} by {book.author}")
Enter fullscreen mode Exit fullscreen mode

Now, Book just stores information about the book, and BookSaver handles saving it to a file. One class, one job!


O - Open/Closed Principle (OCP)

Rule: Your code should be open to adding new stuff but closed to changing old stuff.

Real-World Example:

Consider a TV remote battery. Your remote needs a battery but isn’t dependent on the battery brand. You can use any brand you want, and it will work. So, we can say that the TV remote is loosely coupled with the brand name.

Code Example:

Let’s say we have different shapes, and we want to calculate the area. Instead of adding conditions to handle each shape, we can extend the Shape class.

Before OCP (Bad Example):

def calculate_area(shape):
if shape["type"] == "rectangle":
return shape["width"] * shape["height"]
elif shape["type"] == "circle":
return 3.14 * shape["radius"] ** 2
Enter fullscreen mode Exit fullscreen mode

After OCP:

class Shape:
def area(self):
pass

class Rectangle(Shape):
def **init**(self, width, height):
self.width = width
self. Height = height
def area(self):
    return self.width * self.height

class Circle(Shape):
def **init**(self, radius):
self.radius = radius


def area(self):
    return 3.14 * self.radius ** 2

Enter fullscreen mode Exit fullscreen mode

Now, if we want to add a new shape, we just create a new class without changing the existing code.


L - Liskov Substitution Principle (LSP)

Rule: You should be able to use a child class wherever a parent class is used, without things breaking.

Real-World Example:

Imagine you rent a car. Whether it’s a sedan or an SUV, you should still be able to drive it the same way. You don’t need to learn a different way to drive each type.

Code Example:

If a subclass behaves differently in an unexpected way, it can break things. Here’s an example with a Bird class:

Before LSP (Problematic Example):

class Bird:
def fly(self):
print("Flying")

class Penguin(Bird):
def fly(self):
raise Exception("Penguins can’t fly!")
Enter fullscreen mode Exit fullscreen mode

After LSP:

class Bird:
def move(self):
pass

class FlyingBird(Bird):
def move(self):
print("Flying")

class Penguin(Bird):
def move(self):
print("Swimming")
Enter fullscreen mode Exit fullscreen mode

Now, Penguin doesn’t try to “fly” but uses move() like all Bird types, which avoids errors.


I - Interface Segregation Principle (ISP)

Rule: Don’t force a class to implement stuff it doesn’t use.

Real-World Example:

Think of a remote control. You don’t need TV buttons on a remote for an air conditioner. Each device should have only the controls it needs.

Code Example:

Suppose we have a Worker interface that requires all workers to both work and eat.

Before ISP:

class Worker:
def work(self):
pass

def eat(self):
    pass

class Robot(Worker):
def work(self):
print("Working")

def eat(self):
    raise NotImplementedError("Robots don't eat")

Enter fullscreen mode Exit fullscreen mode

After ISP:

class Workable:
def work(self):
pass

class Eatable:
def eat(self):
pass

class Human(Workable, Eatable):
def work(self):
print("Working")


def eat(self):
    print("Eating")



class Robot(Workable):
def work(self):
print("Working")
Enter fullscreen mode Exit fullscreen mode

Now Robot only implements the work method, without any eat method it doesn’t need.


D - Dependency Inversion Principle (DIP)

Rule: High-level modules shouldn’t depend on low-level modules. Instead, both should depend on abstractions.

Real-World Example:

Imagine your house's electrical system. The lights, fridge, and air conditioning all get power from one main connection, not directly from the power plant. This way, you can swap out appliances without redoing the entire house’s wiring.

Code Example:

Let’s say we have a Keyboard class that directly depends on a WindowsMachine class.

Before DIP (Tight Coupling):

class WindowsMachine:
def type(self):
print("Typing on Windows")

class Keyboard:
def **init**(self, machine):
self.machine = machine

def input(self):
    self.machine.type()

Enter fullscreen mode Exit fullscreen mode

After DIP:

class Machine:
def type(self):
pass

class WindowsMachine(Machine):
def type(self):
print("Typing on Windows")

class MacMachine(Machine):
def type(self):
print("Typing on Mac")

class Keyboard:
def **init**(self, machine: Machine):
self.machine = machine

def input(self):
    self.machine.type()

Enter fullscreen mode Exit fullscreen mode

Now, Keyboard relies on an abstract Machine class, not a specific type of machine. This way, it works with any machine that fits the interface.


Why SOLID is Useful in Real-World Development

In the software industry, code maintenance is a huge part of a developer’s job. Following SOLID principles helps in:

Reducing Bugs: When each part of the code has a single job, it’s easier to spot and fix issues.

Making Changes Easier: Since each class is focused on one task, you can modify or add features without messing up existing code.

Scaling: SOLID principles allow you to build systems that grow over time without falling apart.

While not every developer follows SOLID strictly, these principles are widely respected and used, especially in large projects where flexibility and maintainability are key.

By following SOLID, you make your code like a well-built, organized house—it’s sturdy, easy to upgrade, and keeps everything in its right place.

Top comments (0)