Dependency Inversion Principle (DIP)

August 1, 2024

The last principle in the SOLID segment is the Dependency Inversion Principle (DIP), which suggests that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions.

This means that we should design modules independently of one another. Instead of having a high-level module directly depend on a low-level module, we should create an abstraction that both modules can depend on. By doing this, we can reduce coupling between modules and make our code more flexible and easier to modify over time. The takeaway here is that abstractions should not depend on details; details should depend on abstractions.

As you've seen throughout the example, our pay method in the PaymentHandler class is directly dependent on the Checkout class. This violates the Dependency Inversion Principle, as the PaymentHandler class is a high-level module that depends on the low-level Checkout class.

Let's refactor the code to adhere to the Dependency Inversion Principle:

rom abc import ABC, abstractmethod

class Checkout:
    def __init__(self, payment_handler: 'PaymentHandler'):
        self.products = []
        self.quantity = []
        self.price = []
        self.status = "pending"
        self.payment_handler = payment_handler

    def process_payment(self):
        self.payment_handler.pay(self)


class PaymentHandler(ABC):
    @abstractmethod
    def pay(self, checkout: Checkout):
        pass


class CaptchaVerification(PaymentHandler):
    @abstractmethod
    def verify(self, checkout: Checkout, code: str):
        pass


class CardPayment(CaptchaVerification):
    def __init__(self, code: str):
        self.verification_code = code
        self.pass_captcha = False

    def pay(self, checkout: Checkout):
        if self.pass_captcha:
            print("Processing card payment...")
            checkout.status = "paid"
        else:
            print("Captcha verification required before payment.")

    def verify(self, checkout: Checkout, code: str):
        print("Captcha verification for card payment...")
        self.pass_captcha = True


class PayPalPayment(CaptchaVerification):
    def __init__(self, code: str):
        self.verification_code = code
        self.pass_captcha = False

    def pay(self, checkout: Checkout):
        if self.pass_captcha:
            print("Processing PayPal payment...")
            checkout.status = "paid"
        else:
            print("Captcha verification required before payment.")

    def verify(self, checkout: Checkout, code: str):
        print("Captcha verification for PayPal payment...")
        self.pass_captcha = True


class CryptoPayment(PaymentHandler):
    def __init__(self, token: str):
        self.fingerprint_token = token

    def pay(self, checkout: Checkout):
        print("Processing cryptocurrency payment...")
        checkout.status = "paid"


paypal_handler = PayPalPayment("900913")
checkout = Checkout(paypal_handler)

paypal_processor = checkout.payment_handler
paypal_processor.verify(checkout, "900913")
checkout.process_payment()

crypto_handler = CryptoPayment("0xdeadbeef")
checkout = Checkout(crypto_handler)
checkout.process_payment()

In this refactored code, we've added a new Checkout method called process_payment, which calls the pay method on the PaymentHandler instance associated with the Checkout object. This way, the Checkout class no longer directly depends on the PaymentHandler class, adhering to the Dependency Inversion Principle.

Now, we can pass different payment handlers to the Checkout object and process payments without modifying the Checkout class. This makes our code more flexible and easier to maintain in the long run.

Conclusion

We've gone over all five of the SOLID principles in this series of blog posts. By using these guidelines, you can make your software easier to maintain, more adaptable, and able to grow with your needs. Keep in mind, these principles aren't strict rules; they're more like tips to help you design better software. As you get more experience and tackle different projects, you'll learn how to tweak these principles to fit what you need.

Note: Knowing these principles is really handy for showing off your software design skills in future interviews.

Happy coding! 🚀