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! 🚀