When it comes to writing clean and maintainable code, the SOLID principles are essential tools every developer should know. But hereβs the truth: it's not just about memorizing five fancy terms β it's about recognizing when and how to apply them in real code. You donβt need to recite the definitions β you need to practice them.
The more you apply these principles, the more they become second nature, improving your design decisions over time. Keep this guide handy, and focus on using SOLID whenever you see the opportunity.
Letter | Principle Name | Abbreviation | Key Idea |
---|---|---|---|
S | Single Responsibility Principle | SRP | One class = One responsibility |
O | Open/Closed Principle | OCP | Open to extension, closed to modification |
L | Liskov Substitution Principle | LSP | Subtypes must be substitutable for base types |
I | Interface Segregation Principle | ISP | No forcing of unused methods; favor small, focused interfaces |
D | Dependency Inversion Principle | DIP | Depend on abstractions, not concrete implementations |
π§± S β Single Responsibility Principle (SRP)
β A class should have only one reason to change. Each class should handle a single part of the functionality.
π₯ Bad Example:
class UserManager:
def add_user(self, user):
# Add user to database
pass
def send_email(self, user, message):
# Send welcome email
pass
β Good Example:
class UserManager:
def add_user(self, user):
# Add user to database
pass
class EmailService:
def send_email(self, user, message):
# Send email
pass
π― When & Why:
Use SRP to make your code easier to understand, test, and modify. Changes in one responsibility won't break others.
π§± O β Open/Closed Principle (OCP)
β Software should be open for extension but closed for modification.
π₯ Bad Example:
class Discount:
def get_discount(self, customer_type):
if customer_type == "regular":
return 0.1
elif customer_type == "vip":
return 0.2
β Good Example:
class Discount:
def get_discount(self):
return 0.0
class RegularDiscount(Discount):
def get_discount(self):
return 0.1
class VIPDiscount(Discount):
def get_discount(self):
return 0.2
π― When & Why:
Use it when you expect requirements to evolve. This allows you to add new behavior with new classes without touching existing tested code.
π§± L β Liskov Substitution Principle (LSP)
β Subclasses should be replaceable for their parent classes without altering program behavior.
π₯ Bad Example:
class Bird:
def fly(self):
print("Flying")
class Penguin(Bird):
def fly(self):
raise Exception("Penguins can't fly")
β Good Example:
class Bird:
pass
class FlyingBird(Bird):
def fly(self):
print("Flying")
class Penguin(Bird):
def swim(self):
print("Swimming")
π― When & Why:
The Liskov Substitution Principle (LSP) applies when using inheritance. It ensures that a subclass can stand in for its parent class without breaking the program.
π§± I β Interface Segregation Principle (ISP)
β Clients should not be forced to depend on methods they do not use.
π₯ Bad Example:
class Machine:
def print(self): pass
def scan(self): pass
def fax(self): pass
class OldPrinter(Machine):
def print(self): pass
def scan(self): raise NotImplementedError()
def fax(self): raise NotImplementedError()
β Good Example:
class Printer:
def print(self): pass
class Scanner:
def scan(self): pass
class Fax:
def fax(self): pass
class OldPrinter(Printer):
def print(self): pass
π― When & Why:
Use ISP to keep interfaces lean and focused. This avoids forcing classes to implement irrelevant functionality.
D β Dependency Inversion Principle (DIP)
β High-level modules should depend on abstractions, not on low-level modules.
π₯ Bad Example:
class MySQLDatabase:
def connect(self):
pass
class DataService:
def __init__(self):
self.db = MySQLDatabase()
β Good Example:
class Database:
def connect(self):
pass
class MySQLDatabase(Database):
def connect(self):
pass
class DataService:
def __init__(self, db: Database):
self.db = db
π― When & Why:
Use DIP to make your code more testable and flexible. It allows you to swap out implementations (e.g., use a mock DB during tests).
β Conclusion
The SOLID principles are timeless guidelines for writing clean, modular, and maintainable object-oriented code. By following:
SRP β keep responsibilities focused,
OCP β allow easy feature expansion,
LSP β ensure consistent behavior in subclasses,
ISP β build focused and usable interfaces,
DIP β depend on abstractions for flexibility,
you create software that is easier to test, scale, and adapt to change. Applying SOLID isn't about rigid rules β it's about writing thoughtful, future-proof code.
Top comments (0)