Interface Segregation Principle (ISP)

July 31, 2024

Clients should not be forced to depend on interfaces they do not use. This means that we should split interfaces into smaller, more specific ones, so that clients only need to depend on the interfaces that they actually use.

Let's refactor our PaymentHandler example to demonstrate ISP. We'll introduce a new method specifically for captcha operations that are applicable with CardPayment and PayPalPayment. However, the CryptoPayment class does not support captcha, in turn, breaking the Interface Segregation Principle, by enforcing the captcha method on all payment handlers.

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

    @abstractmethod
    def captcha(self, checkout: Checkout, code: str):
        pass


class CardPayment(PaymentHandler):
    def __init__(self, code: str):
        self.security_token = code
        self.pass_captcha = False

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

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



class PayPalPayment(PaymentHandler):
    def __init__(self, code: str):
        self.security_token = code
        self.pass_captcha = False

    def pay(self, checkout: Checkout):
        print("Processing PayPal payment...")
        checkout.status = "paid"
    
    def captcha(self, checkout: Checkout, code: str):
        print("Captcha verification for card 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"

    def captcha(self, checkout: Checkout):
        raise Exception("Not supported for Crypto payments")



checkout = Checkout()

crypto_processor = CryptoPayment()
crypto_processor.captcha(checkout)

To fix this, we can split the PaymentHandler interface into two separate interfaces: PaymentHandler and CaptchaVerification. This way, clients can depend on the interfaces they actually use, adhering to the Interface Segregation Principle.

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 CaptchaVerification(PaymentHandler):
    @abstractmethod
    def captcha(self, checkout: Checkout, code: str):
        pass


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

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

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



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

    def pay(self, checkout: Checkout):
        print("Processing PayPal payment...")
        checkout.status = "paid"
    
    def captcha(self, checkout: Checkout, code: str):
        print("Captcha verification for card payment...")
        if code == self.security_token:
        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"

checkout = Checkout()

paypal_processor = PayPalPayment("900913")
paypal_processor.captcha(checkout, "d^rkM4tt£r")
paypal_processor.pay(checkout)

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