INTRODUCTION

The solid principle are a set of five design principle for writing maintainable, scalable, and flexible software in object-oriented programming. They were introduced by Robert C. Martin and are widely used as guidelines for designing and organizing classes and relationships in object-oriented systems.

Each letter in the word "SOLID" represents one of the principles:

  • S - Single Responsibility Principle (SRP)
  • O - Open/Closed Principle (OCP)
  • L - Liskov Substitution Principle (LSP)
  • I - Interface Segregation Principle (ISP)
  • D - Dependency Inversion Principle (DIP)


  1. Single Responsibility Principle:

  2. The SRP advocates that a class should have only one reason to change, meaning it should have a single responsibility. This ensures that changes to one aspect of the system have minimal impact on other parts.

    Example: 'Order' Class
    A typical Order class might handle both order processing and order persistence. However, adhering to SRP, we can create separate classes, such as OrderProcessor for processing logic and OrderRepository for data persistence, ensuring that each class has a single responsibility.

    CODE Example

      
        
        class Order:
            def __init__(self, order_id, customer, total_amount):
                self.order_id = order_id
                self.customer = customer
                self.total_amount = total_amount
    
            def process_order(self):
                
                print(f"Order ID: {self.order_id}, Customer: {self.customer}, Total Amount: ${self.total_amount}")
                print("Order processing completed.")
    
            def save_to_database(self):
                
                print("Saving order data to the database...")
    
        
        class OrderProcessor:
            def process_order(self, order):
                
                print(f"Order ID: {order.order_id}, Customer: {order.customer}, Total Amount: ${order.total_amount}")
                print("Order processing completed.")
    
        
        class OrderRepository:
            def save_to_database(self, order):
                # Persistence logic to save the order data to a database
                print("Saving order data to the database...")
    
        
        if __name__ == "__main__":
            order = Order(1, "John Doe", 100)
            order.process_order()
            order.save_to_database()
    
            order_processor = OrderProcessor()
            order_processor.process_order(order)
    
            order_repository = OrderRepository()
            order_repository.save_to_database(order)
    
      
     


  3. Open-Closed Principle:

  4. The OCP suggests that classes should be open for extension but closed for modification, enabling new features to be added without altering existing code.

    Example: 'Shape' Hierarchy
    Consider a Shape base class with an abstract method calculateArea(). We can create specific shape classes like Circle, Rectangle, and Triangle, each implementing calculateArea() based on its specific formula. If we later need to add new shapes, we can create new classes without modifying the existing Shape class.

    CODE Example

      
        
        class PaymentGateway:
            def process_payment(self, amount):
                pass
    
        
        class CreditCardGateway(PaymentGateway):
            def process_payment(self, amount):
                print(f"Processing credit card payment for amount ${amount}")
    
        class PayPalGateway(PaymentGateway):
            def process_payment(self, amount):
                print(f"Processing PayPal payment for amount ${amount}")
    
        
        class PaymentProcessor:
            def __init__(self, payment_gateway):
                self.payment_gateway = payment_gateway
    
            def process_payment(self, amount):
                self.payment_gateway.process_payment(amount)
    
        
        if __name__ == "__main__":
            credit_card_processor = PaymentProcessor(CreditCardGateway())
            paypal_processor = PaymentProcessor(PayPalGateway())
    
            credit_card_processor.process_payment(100)
            paypal_processor.process_payment(50)
      
    


  5. Liskov Substitution Principle:

  6. The LSP ensures that objects of a base class can be replaced by objects of its derived classes without affecting the program's behavior.

    Example: 'Bird' Hierarchy
    Suppose we have a Bird base class with a fly() method. We create two derived classes: Eagle and Penguin. According to LSP, both Eagle and Penguin should be able to substitute Bird without affecting the program's expected behavior, even though Penguin cannot fly.

    CODE Example

      
        
        class Bird:
            def fly(self):
                pass
    
        
        class Eagle(Bird):
            def fly(self):
                return "Soaring high in the sky"
    
        class Penguin(Bird):
            def fly(self):
                return "Sorry, I can't fly. I waddle on the ground."
    
        
        if __name__ == "__main__":
            birds = [Eagle(), Penguin()]
    
            for bird in birds:
                print(f"{bird.__class__.__name__}: {bird.fly()}")
    
      
    


  7. Interface Segregation Principle:

  8. The ISP states that clients should not be forced to depend on methods they do not use, advocating the creation of smaller and more focused interfaces.

    Example: 'Printer' Interface
    If a Printer interface has methods for both printing and scanning, a class that only needs to print should not be forced to implement the scanning method. Instead, we can break the Printer interface into separate Printable and Scannable interfaces, each containing the relevant methods.

    CODE Example

      
        
        class Printable:
            def print_document(self, document):
                pass
    
        
        class Scannable:
            def scan_document(self, document):
                pass
    
        
        class SimplePrinter(Printable):
            def print_document(self, document):
                print(f"Printing: {document}")
    
        
        class SimpleScanner(Scannable):
            def scan_document(self, document):
                print(f"Scanning: {document}")
    
        
        class MultifunctionalPrinter(Printable, Scannable):
            def print_document(self, document):
                print(f"Printing: {document}")
    
            def scan_document(self, document):
                print(f"Scanning: {document}")
    
        
        if __name__ == "__main__":
            simple_printer = SimplePrinter()
            simple_printer.print_document("Simple Document")
    
            simple_scanner = SimpleScanner()
            simple_scanner.scan_document("Simple Document")
    
            multifunctional_printer = MultifunctionalPrinter()
            multifunctional_printer.print_document("Multifunctional Document")
            multifunctional_printer.scan_document("Multifunctional Document")
    
      
    


  9. Dependency Inversion Principle:

  10. The DIP encourages the use of abstractions (interfaces or abstract classes) to decouple high-level modules from low-level modules.

    Example: 'Logger' Class
    Rather than directly depending on a specific logging library, a Logger class can depend on a LoggerInterface, allowing different logging implementations to be injected easily, promoting code flexibility and testability.

    CODE Example

      
        
        class PaymentGateway:
            def process_payment(self, amount):
                pass
    
        
        class CreditCardGateway(PaymentGateway):
            def process_payment(self, amount):
                print(f"Processing credit card payment for amount ${amount}")
    
        class PayPalGateway(PaymentGateway):
            def process_payment(self, amount):
                print(f"Processing PayPal payment for amount ${amount}")
    
        
        class PaymentProcessor:
            def __init__(self, payment_gateway):
                self.payment_gateway = payment_gateway
    
            def process_payment(self, amount):
                self.payment_gateway.process_payment(amount)
    
        
        if __name__ == "__main__":
            credit_card_processor = PaymentProcessor(CreditCardGateway())
            paypal_processor = PaymentProcessor(PayPalGateway())
    
            credit_card_processor.process_payment(100)
            paypal_processor.process_payment(50)
      
    


    Valuable Resources to Learn More

    Conclusion

    The SOLID principles provide a solid foundation for designing maintainable, extensible, and high-quality object-oriented software. Understanding and applying these principles empower developers to create code that is easier to manage, more adaptable to changes, and more reusable across different projects. By adhering to the SOLID principles, you elevate your object-oriented programming skills and contribute to building a more robust software ecosystem.
    Happy coding!