En ingeniería de software existe el principio S.O.L.I.D. Los principios SOLID son guías que pueden ser aplicadas en el desarrollo de software para eliminar malos diseños provocando que el programador tenga que refactorizar hasta que sea legible y extensible.
Sus principios son:
- Single responsability principle - Principio de responsabilidad única.
- Open/closed principle - Principio abierto/cerrado.
- Liskov substitution principle - Principio de sustitución Liskov.
- Interface segregation principle - Principio de segregación de la interfaz.
- Dependency inversion principle - Principio de inversión de la dependencia.
A continuación dejo un vídeo de ArjanCodes que explica con código python los principios S.O.L.I.D:
Los artículos anteriores:
El principio de sustitución de Liskov se define como: Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas.
La idea principal detrás del principio de sustitución de Liskov es que, para cualquier clase, un cliente debería poder usar cualquiera de sus subtipos de manera indistinguible, sin siquiera darse cuenta y, por lo tanto, sin comprometer el comportamiento esperado en tiempo de ejecución. Esto significa que los clientes están completamente aislados y desconocen los cambios en la jerarquía de clases.
- Del principio de abierto y cerrado se creo una clase abstracta y se crearon diferentes clases que heredan de esa clase abstracta para cada proceso de pago. A continuación el código.
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")
La salida es la siguiente:
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 paypal payment type Verifying security code: 0372846
Se tiene un error cuando se creo la clase de proceso de pago de paypal. Para paypal no es necesario un código de seguridad, se necesita un correo electrónico, pero al modificar el método de pago de la clase paypal se estaría violando el principio de sustitución de Liskov.
Para solucionarlo, se elimina el argumento del código de seguridad y se coloca al inicializar el método de pago respectivo.
A continuación el código:
from abc import ABC, abstractmethod 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 # La clase abstracta con el método de pago que ya no maneja el código de seguridad. class PaymentProcessor(ABC): @abstractmethod def pay(self,order): pass # Se define las clases de pago a debito y crédito y se le define el código de seguridad. class DebitPaymentProcessor(PaymentProcessor): def __init__(self,security_code): self.security_code = security_code def pay(self,order): print("Processing debit payment type") print(f"Verifying security code: {self.security_code}") order.status = "paid" 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" # Para el caso de paypal se define el correo electrónico. class PaypalPaymentProcessor(PaymentProcessor): def __init__(self,email_address): self.email_address = email_address def pay(self,order): 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 método de pago debito processor = DebitPaymentProcessor("0372846") processor.pay(order) # Se define el método de pago TC processor = CreditPaymentProcessor("0372846") processor.pay(order) # Se define el método de pago paypal processor = PaypalPaymentProcessor("h@h.com") processor.pay(order)
La salida genera lo siguiente:
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 paypal payment type Verifying email address: h@h.com
- Generación de código QR.
Del artículo sobre el principio de abierto/cerrado se tiene el siguiente código:
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")
El cambio que permitirá cumplir con este principio sería el de inicializar las clases con los kwargs en vez de tenerlo en el método de pago.
from abc import ABC, abstractmethod import qrcode as qrc from MyQR import myqr class GenerateQR(ABC): @abstractmethod def generate(self,text): pass class GenerateMyQR(GenerateQR): def __init__(self,version,**kwargs): self.version = version self.colorize = kwargs.get("colorize", True) self.save_dir = kwargs.get("save_dir","./") self.picture = kwargs.get("picture",None) self.save_name = kwargs.get("save_name","hola1e.png") def generate(self,text): if not self.picture: return myqr.run(words=text, version=my_qr.version, save_name=self.save_name, save_dir=self.save_dir, colorized=self.colorize, contrast=1.0, brightness=1.0) return myqr.run(words=text, version=self.version, save_name=self.save_name, picture=self.picture, save_dir=self.save_dir, colorized=self.colorize, contrast=1.0, brightness=1.0) class GenerateQRCode(GenerateQR): def __init__(self,version,**kwargs): self.version = version self.save_name = kwargs.get("save_name","hola2e.png") self.box_size = kwargs.get("box_size",10) self.border = kwargs.get("border",5) self.fit = kwargs.get("fit",True) self.fill = kwargs.get("fill","black") self.back_color = kwargs.get("back_color","white") def generate(self,text,): # box_size,border,fit,fill,back_color self.qr = qrc.QRCode( version=self.version, box_size=self.box_size, border=self.border ) self.qr.add_data(text) self.qr.make(fit=self.fit) self.img = self.qr.make_image( fill=self.fill, back_color=self.back_color) self.img.save(self.save_name) gen_qr = GenerateMyQR(version=1,{"save_name":"hola2.png"}) gen_qr.generate("Hola mundo") gen_qr = GenerateQRCode(version=1,{"save_name":"hola2.png"}) gen_qr.generate("Hola mundo")
Referencias:
-
Los principios SOLID ilustrados en ejemplos sencillos de Python
-
The 5 (SOLID) Principles of Pyhon Readable Code (enlace roto)
-
How to Write Clean Code (in Python) With SOLID Principles | Principle #3 (enlace roto)
-
The Liskov Substitution Principle (LSP) Explained in Python (enlace roto)
¡Haz tu donativo! Si te gustó el artículo puedes realizar un donativo con Bitcoin (BTC) usando la billetera digital de tu preferencia a la siguiente dirección: 17MtNybhdkA9GV3UNS6BTwPcuhjXoPrSzV
O Escaneando el código QR desde la billetera:
