In software engineering there is the S.O.L.I.D principle. The SOLID principles are guidelines that can be applied in software development to eliminate bad designs, which would otherwise force the programmer to refactor until the code is readable and extensible.

Its principles are:

  • Single responsability principle.
  • Open/closed principle.
  • Liskov substitution principle.
  • Interface segregation principle.
  • Dependency inversion principle.

Below is a video by ArjanCodes that explains the S.O.L.I.D principles with python code:

The previous articles:

The Dependency inversion principle is a specific way of decoupling software modules. By following the principle, the conventional dependency relationships established from high-level policy-setting modules to low-level dependency modules are inverted, which makes the high-level modules independent of the implementation details of the low-level module. The principle states:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g., interfaces).
  2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

The dependency should be on abstractions, not on concretions. High-level modules must not depend on low-level modules. Both low-level and high-level classes should depend on the same abstractions. Abstractions should not depend on details. Details must depend on abstractions.

  1. Here is the code from the previous article on the Interface segregation principle, with the solution using composition.

In this case, passing an object as an argument of a method already violates this principle.


class SMSAuth:
    authorized = False

    def verify_code(self,code):
        print(f"Verifying code {code}")
        self.authorized = True

    def is_authorized(self) -> bool:
        return self.authorized 

# Clase abstracta procesamiento de pago con método pay.
class PaymentProcessor(ABC):
    @abstractmethod
    def pay(self,order):
        pass 

# Clase procesamiento pago por tarjeta de debito que hereda de la clase abstracta.
# el método init recibe como argumentos el código de seguridad y la clase SMSAuth para autorizar el pago.
class DebitPaymentProcessor(PaymentProcessor):

    def __init__(self,security_code, authorizer: SMSAuth):

        self.authorizer = authorizer
        self.security_code = security_code

    def pay(self,order):
        # Se consulta si no está autorizado el pago
        # al no tener autorización devuelve una excepción.
        if not self.authorizer.is_authorized():
            raise Exception("Not authorized")
        print("Processing debit payment type")
        print(f"Verifying security code: {self.security_code}")
        order.status = "paid"

# Clase de procesamiento de pago con TC, que hereda de la clase abstracta.
# En este caso no se necesita la clase SMSAuth, sólo el código de seguridad.
class CreditPaymentProcessor(PaymentProcessor):

    def __init__(self,security_code):
        self.security_code = security_code


    def pay(self,order):
        print("Processing credit payment type")
        print(f"Verifying security code: {self.security_code}")
        order.status = "paid"

# Clase de procesamiento de pago vía paypal, hereda de la misma clase abstracta.
# El init recibe de argumentos dirección de correo y el objecto SMSAuth.
class PaypalPaymentProcessor(PaymentProcessor):

    def __init__(self,email_address,authorizer:SMSAuth):
        self.authorizer = authorizer
        self.email_address = email_address
        self.verified = False


    def pay(self,order):
        # Se valida si se tiene autorización para realizar el pago.
        if not self.authorizer.is_authorized():
            raise Exception("Not authorized")
        print("Processing paypal payment type")
        print(f"Verifying email address: {self.email_address}")
        order.status = "paid"

# Ahora se instancia la clase Order, y se agrega los items a comprar en la orden.

order = Order()
order.add_item("Teclado", 1, 50)
order.add_item("Memoria", 1, 150)
order.add_item("Cable USB", 2, 5)

# Imprime el precio total de la orden
print(order.total_price())

# Se define el autorizador.
authorizer = SMSAuth()

# Se define el método de pago debito
# ahora se le pasa el código y el autorizador.

processor = DebitPaymentProcessor("0372846",authorizer)
# Se verifica el pago
authorizer.verify_code(454545)

# Realiza el pago de la orden.
processor.pay(order)

# Se define el método de pago TC
# Para este caso se mantiene igual.
processor = CreditPaymentProcessor("0372846")
processor.pay(order)



# Se define el método de pago paypal
# Se le pasa el correo y el autorizador.
processor = PaypalPaymentProcessor("h@h.com",authorizer)
# Se realiza el pago de la orden.
processor.pay(order)

The output of this execution is the following:


210
Verifying code 454545
Processing debit payment type
Verifying security code: 0372846

Processing debit payment type
Verifying security code: 0372846

Processing credit payment type
Verifying security code: 0372846

Processing paypal payment type
Verifying email address: h@h.com

Solution

To comply with the dependency inversion principle, an abstract class is created that handles whether authentication is verified, together with the AuthSMS class. In the respective payment processing classes that need to be verified by SMS, instead of passing the object as an argument, the abstract class is passed.

Here is the code:


from abc import ABC, abstractmethod

# La clase Order 
class Order:

    def __init__(self):
        self.items = []
        self.quantities = []
        self.prices = []
        self.status = "open"

    def add_item(self, name, quantity, price):
        self.items.append(name)
        self.quantities.append(quantity)
        self.prices.append(price)

    def total_price(self):
        total = 0
        for i in range(len(self.prices)):
            total += self.quantities[i] * self.prices[i]
        return total

# Clase abstracta authorizer con método que devuelve si está autorizado o no. 
class Authorizer(ABC):
    @abstractmethod
    def is_authorized(self) ->bool:
        pass


# Clase abstracta de procesamiento de pago
class PaymentProcessor(ABC):
    @abstractmethod
    def pay(self,order):
        pass 

# Clase SMSAutho que hereda de la clase abstracta authorizer
class SMSAuth(Authorizer):
    authorized = False

    def verify_code(self,code):
        print(f"Verifying code {code}")
        self.authorized = True

    def is_authorized(self) -> bool:
        return self.authorized

# clase pago por tarjeta de debito que recibe en el init authorizer que es la clase abstracta.
class DebitPaymentProcessor(PaymentProcessor):

    def __init__(self,security_code, authorizer: Authorizer):

        self.authorizer = authorizer
        self.security_code = security_code

    def pay(self,order):
        # Se consulta si no está autorizado el pago
        # al no tener autorización devuelve una excepción.
        if not self.authorizer.is_authorized():
            raise Exception("Not authorized")
        print("Processing debit payment type")
        print(f"Verifying security code: {self.security_code}")
        order.status = "paid"

# La clase de procesamiento de TC se mantiene igual.
class CreditPaymentProcessor(PaymentProcessor):

    def __init__(self,security_code):
        self.security_code = security_code


    def pay(self,order):
        print("Processing credit payment type")
        print(f"Verifying security code: {self.security_code}")
        order.status = "paid"


# clase dde procesamiento de pago por paypal
# Se le pasa como argumento la clase abstracta.
class PaypalPaymentProcessor(PaymentProcessor):

    def __init__(self,email_address,authorizer:Authorizer):
        self.authorizer = authorizer
        self.email_address = email_address
        self.verified = False


    def pay(self,order):
        # Se valida si se tiene autorización para realizar el pago.
        if not self.authorizer.is_authorized():
            raise Exception("Not authorized")
        print("Processing paypal payment type")
        print(f"Verifying email address: {self.email_address}")
        order.status = "paid"

# Ahora se instancia la clase Order, y se agrega los items a comprar en la orden.

order = Order()
order.add_item("Teclado", 1, 50)
order.add_item("Memoria", 1, 150)
order.add_item("Cable USB", 2, 5)

# Imprime el precio total de la orden
print(order.total_price())

# Se define el autorizador.
authorizer = SMSAuth()

# Se define el método de pago debito
# ahora se le pasa el código y el autorizador.

processor = DebitPaymentProcessor("0372846",authorizer)
# Se verifica el pago
authorizer.verify_code(454545)

# Realiza el pago de la orden.
processor.pay(order)

# Se define el método de pago TC
# Para este caso se mantiene igual.
processor = CreditPaymentProcessor("0372846")
processor.pay(order)



# Se define el método de pago paypal
# Se le pasa el correo y el autorizador.
processor = PaypalPaymentProcessor("h@h.com",authorizer)
# Se realiza el pago de la orden.
processor.pay(order)

The output is the following:


210
Verifying code 454545
Processing debit payment type
Verifying security code: 0372846

Processing debit payment type
Verifying security code: 0372846

Processing credit payment type
Verifying security code: 0372846

Processing paypal payment type
Verifying email address: h@h.com


What was achieved is hiding the implementation of the composition that is passed as an argument in the payment processors that have to validate via SMS.

In the next article

References:


Make a donation! If you liked the article you can make a donation in Bitcoin (BTC) using the digital wallet of your choice to the following address: 17MtNybhdkA9GV3UNS6BTwPcuhjXoPrSzV

Or by scanning the QR code from your wallet:

17MtNybhdkA9GV3UNS6BTwPcuhjXoPrSzV