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 - Single responsibility principle.
- Open/closed principle - Open/closed principle.
- Liskov substitution principle - Liskov substitution principle.
- Interface segregation principle - Interface segregation principle.
- Dependency inversion principle - Dependency inversion principle.
Below is a video by ArjanCodes that explains the S.O.L.I.D principles with python code:
The previous article is about the single responsibility principle.
The open/closed principle states that a software entity (class, module, function, etc) must remain open for extension, but closed for modification. That is, it must be possible to extend the behavior of such an entity without modifying its source code.
In other words, the code should be written in such a way that, when adding new functionality, you do not have to modify previously written code, which may be in use by other users
- From the single responsibility principle we have the following code, where the Order class was separated from the PaymentProcess class:
# Ahora se tiene una clase que sólo manejará métodos de la orden y se separa el método de pago en otra clase llamada PaymentProcessor
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 PaymentProcessor con los métodos: pago con debito y pago a credito.
class PaymentProcessor:
def pay_debit(self, order, security_code):
print("Processing debit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
def pay_credit(self, order, security_code):
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
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())
# Instancia la clase de procesador de pago y se llama al método pagar con debito pasando como argumento la orden y el código de seguridad de la tarjeta.
processor = PaymentProcessor()
processor.pay_debit(order, "0372846")
The output is the following:
210
Processing debit payment type
Verifying security code: 0372846
To comply with the open/closed principle, an Abstract class that defines the payment Process will be used, and a class is created for each payment type that inherits from that abstract class; now the payment process methods are separated into classes with a single method.
from abc import ABC, abstractmethod
# La clase Order se mantiene igual:
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
# Se crea una clase abstracta del proceso de pago:
class PaymentProcessor(ABC):
@abstractmethod
def pay(self,order,security_code):
pass
# Se crea la clase de pago con debito que hereda de la clase abstracta
class DebitPaymentProcessor(PaymentProcessor):
def pay(self,order,security_code):
print("Processing debit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
# Se crea la clase de pago con TC que hereda de la clase abstracta
class CreditPaymentProcessor(PaymentProcessor):
def pay(self,order,security_code):
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
# Se crea el método de pago paypal
class PaypalPaymentProcessor(PaymentProcessor):
def pay(self,order,security_code):
print("Processing paypal payment type")
print(f"Verifying security code: {security_code}")
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 método de pago debito
processor = DebitPaymentProcessor()
processor.pay(order, "0372846")
# Se define el método de pago TC
processor = CreditPaymentProcessor()
processor.pay(order, "0372846")
# Se define el método de pago paypal
processor = PaypalPaymentProcessor()
processor.pay(order, "0372846")
The output is the following:
210
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 credit payment type
Verifying security code: 0372846
Now, when a new payment method is required, you simply create a class for that method that inherits from the abstract class; there is no longer any need to touch the classes or methods of the other payment methods.
- QR code generation.
From the article on the single responsibility principle we have the following code:
class GenerateQR:
def __init__(self,version):
self.version = version
def myqr(self,my_qr,text,save_name,colorize,save_dir,picture=None):
if not picture:
return myqr.run(words=text, version=my_qr.version, save_name=save_name,
save_dir=save_dir, colorized=colorize, contrast=1.0, brightness=1.0)
return myqr.run(words=text, version=my_qr.version, save_name=save_name, picture=picture,
save_dir=save_dir, colorized=colorize, contrast=1.0, brightness=1.0)
def qrcode(self,my_qr,text,save_name,box_size,border,fit,fill,back_color):
self.qr = qrc.QRCode(
version=my_qr.version,
box_size=box_size,
border=border
)
self.qr.add_data(text)
self.qr.make(fit=fit)
self.img = self.qr.make_image(
fill=fill, back_color=back_color)
self.img.save(save_name)
gen_qr = GenerateQR(version=1)
# Generar QR con myqr
gen_qr.myqr(my_qr,"hola mundo!","hola.png",True,"./")
# generar qr con qrcode
gen_qr.qrcode(my_qr,"hola mundo2!",'hola2.png',10,5,True,'black','white')
There is a single class with one method for each library, but if you want to add a new library you have to modify the class, and this breaks the principle. To avoid it, an abstract class is created, plus one class per library that inherits from the abstract class; that way, if there is a new library, you just create a new class without needing to modify the existing classes.
from abc import ABC, abstractmethod
import qrcode as qrc
from MyQR import myqr
# Clase abstracta
class GenerateQR(ABC):
@abstractmethod
def generate(self,my_qr,text,save_name,**kwargs):
pass
# Clase para la librería MyQR que hereda de la clase abstracta.
class GenerateMyQR(GenerateQR):
def __init__(self, version):
self.version = version
def generate(self,my_qr,text,save_name,**kwargs):
# colorize,save_dir,picture=None
colorize = kwargs.get("colorize", True)
save_dir = kwargs.get("save_dir","./")
picture = kwargs.get("picture",None)
if not picture:
return myqr.run(words=text, version=self.version, save_name=save_name,
save_dir=save_dir, colorized=colorize, contrast=1.0, brightness=1.0)
return myqr.run(words=text, version=my_qr.version, save_name=save_name, picture=picture,
save_dir=save_dir, colorized=colorize, contrast=1.0, brightness=1.0)
# Clase de la librería qr_code que hereda de la clase abstracta.
class GenerateQRCode(GenerateQR):
def __init__(self, version):
self.version = version
def generate(self,my_qr,text,save_name,**kwargs):
# box_size,border,fit,fill,back_color
box_size = kwargs.get("box_size",10)
border = kwargs.get("border",5)
fit = kwargs.get("fit",True)
fill = kwargs.get("fill","black")
back_color = kwargs.get("back_color","white")
self.qr = qrc.QRCode(
version=self.version,
box_size=box_size,
border=border
)
self.qr.add_data(text)
self.qr.make(fit=fit)
self.img = self.qr.make_image(
fill=fill, back_color=back_color)
self.img.save(save_name)
# Se crea la instancia de la clase pasandole la versión del código QR que se quiere usar
# para la librería MyQR y luego para qr_code
gen_qr = GenerateMyQR(version=1)
gen_qr.generate(my_qr,"Hola mundo","hola1c.png")
gen_qrcode = GenerateQRCode()
gen_qrcode.generate(my_qr,"Hola mundo","hola2c.png")
As explained, it is no longer necessary to touch the code of the existing library classes, and if there is a new library you only need to create a new class inheriting from the abstract class.
References:
-
Los principios SOLID ilustrados en ejemplos sencillos de Python
-
The 5 (SOLID) Principles of Pyhon Readable Code (broken link)
-
How to Write Clean Code (in Python) With SOLID Principles | Principle #2 (broken link)
Make a donation! If you liked the article you can make a donation with Bitcoin (BTC) using the digital wallet of your choice to the following address: 17MtNybhdkA9GV3UNS6BTwPcuhjXoPrSzV
Or by scanning the QR code from your wallet:
