Liskov Substitution Principle (LSP)

July 30, 2024

This principle suggests that we should be able to replace objects with instances of their subtypes or subclasses without affecting the correctness of the program.

In simpler terms, you should be able to use an object of a child class in place of an object of its parent class without changing how the program behaves.

To demonstrate, we need to ensure that the child class has "is-a" relationship with the parent class. This means the child class must follow all the rules and expectations set by the parent class. By doing this, we maintain consistent behavior and ensure that our program remains reliable and predictable.

Let's refactor our PaymentHandler class to demonstrate LSP. We will first remove the dependency from security_token at the parent class level and move it to the child classes, so each payment handler can have its own unique verification method.

Then we'll add new __init__ methods to the child classes to set independent verification methods, isolating the verification logic for each payment handler.

Note: How our new class CryptoPayment has a different verification method compared to CardPayment and PayPalPayment. This demonstrates that we can substitute different payment handlers without affecting the behavior of the Checkout class instance.

from abc import ABC, abstractmethod

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


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


class CardPayment(PaymentHandler):
    def __init__(self, token: str):
        self.security_token = token

    def pay(self, checkout: Checkout):
        print("Verifying payment with token:", self.security_token)
        print("Processing card payment...")
        checkout.status = "paid"


class PayPalPayment(PaymentHandler):
    def __init__(self, token: str):
        self.security_token = token

    def pay(self, checkout: Checkout):
        print("Verifying payment with token:", self.security_token)
        print("Processing PayPal payment...")
        checkout.status = "paid"


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

    def pay(self, checkout: Checkout):
        print("Verifying crypto payment with fingerprint:", self.fingerprint_token)
        print("Processing cryptocurrency payment...")
        checkout.status = "paid"

checkout = Checkout()

paypal_processor = PayPalPayment("900913")
paypal_processor.pay(checkout)

crypto_processor = CryptoPayment("0xdeadbeef")
crypto_processor.pay(checkout)