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}")
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}")
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
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
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!")
After LSP:
class Bird:
def move(self):
pass
class FlyingBird(Bird):
def move(self):
print("Flying")
class Penguin(Bird):
def move(self):
print("Swimming")
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")
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")
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()
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()
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)