The Open-Closed Principle (OCP) is a core principle from SOLID that states "software entities should be open for extension but closed for modification." Let me demonstrate this with a clear example:
# Bad Example - Violates Open-Closed Principle
class DiscountCalculator:
def calculate_discount(self, product_type: str, price: float) -> float:
if product_type == "electronics":
return price * 0.9 # 10% discount
elif product_type == "clothing":
return price * 0.85 # 15% discount
elif product_type == "furniture":
return price * 0.75 # 25% discount
# Adding a new product type requires modifying existing code
return price
# Good Example - Follows Open-Closed Principle
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def calculate_discount(self, price: float) -> float:
pass
class ElectronicsDiscount(DiscountStrategy):
def calculate_discount(self, price: float) -> float:
return price * 0.9 # 10% discount
class ClothingDiscount(DiscountStrategy):
def calculate_discount(self, price: float) -> float:
return price * 0.85 # 15% discount
class FurnitureDiscount(DiscountStrategy):
def calculate_discount(self, price: float) -> float:
return price * 0.75 # 25% discount
# Adding a new discount type without modifying existing code
class BookDiscount(DiscountStrategy):
def calculate_discount(self, price: float) -> float:
return price * 0.95 # 5% discount
class Product:
def __init__(self, name: str, price: float, discount_strategy: DiscountStrategy):
self.name = name
self.price = price
self.discount_strategy = discount_strategy
def get_discounted_price(self) -> float:
return self.discount_strategy.calculate_discount(self.price)
# Example usage
def main():
# Create products with different discount strategies
laptop = Product("Laptop", 1000.0, ElectronicsDiscount())
shirt = Product("Shirt", 50.0, ClothingDiscount())
chair = Product("Chair", 200.0, FurnitureDiscount())
book = Product("Book", 30.0, BookDiscount())
# Calculate discounted prices
products = [laptop, shirt, chair, book]
for product in products:
original_price = product.price
discounted_price = product.get_discounted_price()
discount_percentage = ((original_price - discounted_price) / original_price) * 100
print(f"{product.name}:")
print(f" Original price: ${original_price:.2f}")
print(f" Discounted price: ${discounted_price:.2f}")
print(f" Discount applied: {discount_percentage:.1f}%\\\\n")
# Custom Holiday Discount - Adding new functionality without modifying existing code
class HolidayDiscount(DiscountStrategy):
def __init__(self, extra_discount: float):
self.extra_discount = extra_discount
def calculate_discount(self, price: float) -> float:
return price * (1 - self.extra_discount)
if __name__ == "__main__":
main()
# Using the new holiday discount
holiday_product = Product("Holiday Special", 100.0, HolidayDiscount(0.3))
discounted_price = holiday_product.get_discounted_price()
print(f"Holiday Special:")
print(f" Original price: ${100.0:.2f}")
print(f" Discounted price: ${discounted_price:.2f}")
print(f" Discount applied: {30.0:.1f}%")
Let's break down why the second example follows the Open-Closed Principle:
DiscountStrategy
class never needs to changeProduct
class remains unchangedBookDiscount
and HolidayDiscount
classes were added without changing any existing codeThe bad example would require modifying the existing calculate_discount
method every time a new product type is added, which:
Would you like me to show: