In the realm of software development, the SOLID principles are a set of five design principles aimed at creating robust, maintainable, and scalable software systems. These principles, coined by Robert C. Martin (also known as Uncle Bob), provide a guideline for developers to follow to ensure their codebase is clean and extensible. Here, we will explore each of the SOLID principles and demonstrate how to implement them with examples in Python.
1. Single Responsibility Principle (SRP)
Definition: A class should have only one reason to change, meaning it should have only one job or responsibility.
Example:
class Order:
def __init__(self, items):
self.items = items
def calculate_total(self):
return sum(item.price for item in self.items)
class InvoicePrinter:
@staticmethod
def print_invoice(order):
print("Invoice:")
for item in order.items:
print(f"{item.name}: ${item.price}")
print(f"Total: ${order.calculate_total()}")
# Usage
class Item:
def __init__(self, name, price):
self.name = name
self.price = price
items = [Item("Apple", 1), Item("Banana", 2)]
order = Order(items)
InvoicePrinter.print_invoice(order)
In this example, the Order class is responsible only for managing the order, while the InvoicePrinter class is responsible for printing the invoice. This adheres to SRP by ensuring each class has a single responsibility.
2. Open/Closed Principle (OCP)
Definition: Software entities should be open for extension but closed for modification.
Example:
class Discount:
def apply(self, total):
return total
class PercentageDiscount(Discount):
def __init__(self, percentage):
self.percentage = percentage
def apply(self, total):
return total - (total * self.percentage / 100)
class FixedDiscount(Discount):
def __init__(self, amount):
self.amount = amount
def apply(self, total):
return total - self.amount
def calculate_total(order, discount):
total = order.calculate_total()
return discount.apply(total)
# Usage
discount = PercentageDiscount(10)
print(calculate_total(order, discount))
In this example, the Discount class is extended by PercentageDiscount and FixedDiscount without modifying the base class, adhering to OCP.
3. Liskov Substitution Principle (LSP)
Definition: Subtypes must be substitutable for their base types without altering the correctness of the program.
Example:
class Bird:
def fly(self):
pass
class Sparrow(Bird):
def fly(self):
print("Sparrow is flying")
class Ostrich(Bird):
def fly(self):
raise Exception("Ostrich can't fly")
def make_bird_fly(bird):
bird.fly()
# Usage
sparrow = Sparrow()
make_bird_fly(sparrow)
ostrich = Ostrich()
try:
make_bird_fly(ostrich)
except Exception as e:
print(e)
Here, Ostrich violates LSP because it cannot fly, thus it is not substitutable for the Bird base class.
4. Interface Segregation Principle (ISP)
Definition: Clients should not be forced to depend on interfaces they do not use.
Example:
from abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def print_document(self, document):
pass
class Scanner(ABC):
@abstractmethod
def scan_document(self, document):
pass
class MultiFunctionPrinter(Printer, Scanner):
def print_document(self, document):
print(f"Printing: {document}")
def scan_document(self, document):
print(f"Scanning: {document}")
class SimplePrinter(Printer):
def print_document(self, document):
print(f"Printing: {document}")
# Usage
mfp = MultiFunctionPrinter()
mfp.print_document("Report")
mfp.scan_document("Report")
printer = SimplePrinter()
printer.print_document("Report")
In this example, the MultiFunctionPrinter implements both Printer and Scanner interfaces, while SimplePrinter only implements Printer, adhering to ISP.
5. Dependency Inversion Principle (DIP)
Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Example:
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def save(self, data):
pass
class MySQLDatabase(Database):
def save(self, data):
print("Saving data to MySQL database")
class MongoDBDatabase(Database):
def save(self, data):
print("Saving data to MongoDB database")
class UserService:
def __init__(self, database: Database):
self.database = database
def save_user(self, user_data):
self.database.save(user_data)
# Usage
mysql_db = MySQLDatabase()
mongo_db = MongoDBDatabase()
user_service = UserService(mysql_db)
user_service.save_user({"name": "John Doe"})
user_service = UserService(mongo_db)
user_service.save_user({"name": "Jane Doe"})
In this example, the UserService depends on the Database abstraction, allowing for flexibility and adhering to DIP.
Conclusion
By adhering to the SOLID principles, developers can create software that is more modular, easier to maintain, and scalable. These principles help in managing the complexities of software development, ensuring that code remains clean and extensible. Through practical examples in Python, we can see how these principles can be applied to create robust and maintainable systems.
Top comments (0)