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 single responsibility principle states that each module or class should have responsibility over a single part of the functionality provided, and this responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.
I will use Arjan's code example and then show the code of a QR code generator in python and how, by following the S.O.L.I.D. principle, its readability and extensibility are improved.
- Order class that has items, quantities, prices and status. With the methods add_item (add an item), total_price (total price) and pay (payment).
class Order:
def __init__(self):
self.items = []
self.quantities = []
self.prices = []
self.status = "open"
def add_item(self, name, quantity, price):
# Agrega un item con la cantidad y precio a la orden.
self.items.append(name)
self.quantities.append(quantity)
self.prices.append(price)
def total_price(self):
# Retorna el precio total a pagar.
total = 0
for i in range(len(self.prices)):
total += self.quantities[i] * self.prices[i]
return total
def pay(self, payment_type, security_code):
# Genera el pago según la forma de pago, debito o crédito.
if payment_type == "debit":
print("Processing debit payment type")
print(f"Verifying security code: {security_code}")
self.status = "paid"
elif payment_type == "credit":
print("Processing credit payment type")
print(f"Verifying security code: {security_code}")
self.status = "paid"
else:
raise Exception(f"Unknown payment type: {payment_type}")
# Se instancia la orden con los items, sus cantidades y precios.
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 la forma de pago.
order.pay("debit", "0372846")
The output generated is the total amount to pay and then the payment method:
210
Processing debit payment type
Verifying security code: 0372846
The problem with the order class is that if you want to add another payment method, you have to modify that class, which belongs to the order, when that class should not care about the payment method; it should simply create the Order.
To improve this, a class called PaymentProcessor (payment processor) will be created, which is the one that will handle the different payment methods.
Below is the code:
# 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")
This returns the same as in the previous code.
210
Processing debit payment type
Verifying security code: 0372846
The advantage of this new version is that if you need to add a new payment method, for example payment with cryptocurrencies or paypal, you do not need to touch the Order class, only the PaymentProcessor class.
- QR code generator.
There are libraries to generate QR codes: MyQR, qrcode and amzqr.
from MyQR import myqr
import qrcode as qrc
from amzqr import amzqr
class GenQR:
def __init__(self, version, *args, **kwargs):
self.__version = version
self.__box_size = kwargs.get("box_size", None)
self.__border = kwargs.get("border", None)
self.__fit = kwargs.get("fit", None)
self.__fill = kwargs.get("fill", None)
self.__back_color = kwargs.get("back_color", None)
self.__colorize = kwargs.get("colorize", None)
self.__picture = kwargs.get("picture", None)
self.__save_dir = kwargs.get("save_dir", None)
def generate(self, library="MyQR", text, save_name):
# Genera código QR y lo guarda en un archivo
#Se genera el código dependiendo de la librería.
if library == "MyQR":
if not self.__picture:
resp = myqr.run(words=text, version=self.__version, save_name=save_name,
save_dir=self.__save_dir, colorized=self.__colorize, contrast=1.0, brightness=1.0, save_format='PNG')
else:
resp = myqr.run(words=text, version=self.__version, save_name=save_name, picture=self.__picture,
save_dir=self.__save_dir, colorized=self.__colorize, contrast=1.0, brightness=1.0, save_format='PNG')
return resp
self.__qr = qrc.QRCode(
version=self.__version,
)
self.__qr.add(text)
self.__qr.make(fit=self.__fit)
self.__img = self.__qr.make_image(
fill=self.__fill, back_color=self.__back_color)
self.__img.save(save_name)
# Se crea la instancia
genqr = GenQR(version=1)
genqr.generate(library='MyQR',text='Hola mundo!',save_name='hola.png')
Here the problem is that there is one method whose QR code generation depends on the library used, but if a new library has to be added, you will probably have to modify the arguments it receives and add code for the new library in that method. The solution is to create methods for each library.
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')
Another option is to create a class for each library.
The next article will explain the open/closed principle.
References:
-
The 5 (SOLID) Principles of Pyhon Readable Code (broken link)
-
How to Write Clean Code (in Python) With SOLID Principles | Principle #1 (broken link)
-
The Single Responsibility Principle Explained in Python (broken link)
Make a donation! If you liked this article you can donate with Bitcoin (BTC) using the digital wallet of your choice to the following address: 17MtNybhdkA9GV3UNS6BTwPcuhjXoPrSzV
Or by scanning the QR code from your wallet:
