Overview Writing software that lasts requires more than just making code run; it requires making code maintainable. The SOLID principles, popularized by Robert C. Martin (Uncle Bob), provide a roadmap for creating robust, flexible, and decoupled Object-Oriented Programming systems. By applying these five design rules, developers transform brittle, "spaghetti" code into a modular architecture where changes in one area don't trigger a cascade of failures elsewhere. This tutorial refactors a standard Python sales system to demonstrate these principles in action. Prerequisites To get the most out of this guide, you should possess a firm grasp of Python basics, particularly Classes, Inheritance, and Method overriding. Familiarity with the concept of Abstract Base Classes will help when we move toward interface design. Key Libraries & Tools * **abc**: Python’s built-in module for defining Abstract Base Classes, essential for enforcing interface contracts. * **Python 3.x**: The primary runtime environment for executing our refactored logic. Code Walkthrough 1. Single Responsibility (SRP) We start with an `Order` class that handles items and payment logic. This is high coupling. We extract the payment logic into a separate `PaymentProcessor` class. Now, `Order` only tracks items, while `PaymentProcessor` handles the transaction. ```python class Order: def __init__(self): self.items = [] self.status = "open" class PaymentProcessor: def pay_debit(self, order, security_code): print(f"Paying debit: {security_code}") order.status = "paid" ``` 2. Open/Closed (OCP) Adding new payment methods shouldn't mean modifying existing classes. We use the abc module to create an abstract interface. Now, adding Bitcoin or PayPal simply requires a new subclass. ```python from abc import ABC, abstractmethod class PaymentProcessor(ABC): @abstractmethod def pay(self, order): pass ``` 3. Liskov Substitution (LSP) If a `Paypal` subclass requires an email but a `Debit` subclass requires a security code, changing the `pay` method's signature breaks the program. We solve this by moving specific credentials to the `__init__` method, keeping the `pay` method signature identical across all subtypes. 4. Interface Segregation & Dependency Inversion (ISP/DIP) Instead of forcing every processor to implement Two-Factor Authentication (2FA), we use **Composition**. We create an `Authorizer` class and inject it into the processors. This follows DIP: our high-level payment logic depends on an abstract `Authorizer` rather than a concrete `SMS_Authorizer`. ```python class DebitPaymentProcessor(PaymentProcessor): def __init__(self, security_code, authorizer: Authorizer): self.authorizer = authorizer self.security_code = security_code def pay(self, order): if not self.authorizer.is_authorized(): raise Exception("Not authorized") order.status = "paid" ``` Syntax Notes Python uses the `@abstractmethod` decorator to identify methods that subclasses MUST implement. Note the use of **Type Hinting** (e.g., `authorizer: Authorizer`) which, while not enforced at runtime, is vital for clarity and IDE support in complex architectures. Practical Examples These principles shine in e-commerce backends, plugin systems, and API integrations. If you need to support multiple shipping carriers (FedEx, UPS, DHL), SRP and OCP allow you to add new carriers without touching the core shipping logic. Tips & Gotchas Avoid over-engineering. While SOLID is powerful, applying it to a three-line script adds unnecessary complexity. Prefer **Composition over Inheritance** to avoid deep, confusing class hierarchies. If a subclass method is just raising a `NotImplementedError`, you are likely violating the Liskov Substitution Principle.
Class
Concepts
- Apr 23, 2021