S-O-L-I-D és un conjunt de principis de disseny orientat a objectes que busca millorar la qualitat i la flexibilitat del software. L'acrònim SOLID representa cinc principis fonamentals que ajuden els desenvolupadors a crear sistemes de software més robust, mantenibles i escalables. Aquests principis són:
S - Single Responsibility Principle: Cada classe ha de tenir una única responsabilitat o raó per a canviar.
O - Open/Closed Principle: El software ha d'estar obert per a la seva extensió, però tancat per a la seva modificació.
L - Liskov Substitution Principle: Els objectes d'una classe derivada han de poder substituir-se per objectes de la classe base sense alterar el funcionament del programa.
I - Interface Segregation Principle: Els clients no han d'estar obligats a dependre d'interfícies que no utilitzen.
D - Dependency Inversion Principle: Les dependències han d'estar en abstracció, no en detalls concrets.
En aquest blog, coneixeràs cada principi de l'enfocament S-O-L-I-D per a entendre els seus fonaments i com aplicar-los en el disseny orientat a objectes.
El principi de responsabilitat única(Single Responsibility Principle) estableix que una classe ha de tenir només una raó per a canviar, la qual cosa significa que ha de tenir només una tasca o responsabilitat dins del sistema de software. En centrar cada classe en una única preocupació, el principi de responsabilitat única redueix la complexitat, millora la llegibilitat i fa que el codi sigui més fàcil de mantenir i ampliar.
Exemple:
class Factura:
def __init__(self, quantitat):
self.quantitat = quantitat
def imprimir_factura(self):
# Responsabilitat: Imprimir factura
print(f"Quantitat de la factura: {self.quantitat}")
class PersistenciaFactura:
def guardar_a_arxiu(self, factura):
# Responsabilitat: Guardar factura a un arxiu
with open('factura.txt', 'w') as f:
f.write(f"Quantitat de la factura: {factura.quantitat}")
# Separar responsabilitats en diferentes classes
factura = Factura(100)
factura.imprimir_factura()
persistencia = PersistenciaFactura()
persistencia.guardar_a_arxiu(factura)
El principi obert/tancat(Open/Closed Principle) emfatitza que les entitats de software (classes, mòduls, funcions, etc.) han d'estar obertes a l'extensió però tancades a la modificació. Aquest principi encoratja als desenvolupadors a dissenyar sistemes d'una manera que permeti agregar noves funcionalitats a través de l'extensió en lloc de canviar el codi existent, minimitzant així el risc d'introduir errors en el codi que ja funciona.
Exemple:
from abc import ABC, abstractmethod
class Figura(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Figura):
def __init__(self, amplada, altura):
self.amplada = amplada
self.altura = altura
def area(self):
return self.amplada * self.altura
class Cercle(Figura):
def __init__(self, radi):
self.radi = radi
def area(self):
return 3.14 * self.radi * self.radi
def calcular_area_total(figures):
total = 0
for figura in figures:
total += figura.area()
return total
figures = [Rectangle(10, 20), Cercle(5)]
print(calcular_area_total(figures))
El principi de substitució de Liskov(Liskov Substitution Principle) defineix que els objectes d'una superclase han de poder substituir-se per objectes de les seves subclasses sense afectar la correcció del programa. En altres paraules, les classes derivades han de poder substituir a les seves classes base sense alterar les propietats desitjables del programa, la qual cosa garanteix la coherència en el comportament i el disseny.
Exemple:
class Au:
def volar(self):
print("Volant")
class Pardal(Au):
def volar(self):
print("Pardal volant")
class Estrus(Au):
def volar(self):
raise Exception("L'Estruç no pot volar")
def fer_volar(Au: Au):
Au.volar()
pardal = Pardal()
fer_volar(pardal)
Estrus = Estrus()
# fer_volar(Estrus) # Això provocarà una excepció violant l'LSP
El principi de segregació d'interfícies(Interface Segregation Principle) està a favor de dissenyar interfícies específiques per a cada client i de granularitat fina en lloc d'una única interfície gran que atengui múltiples clients. En segregar les interfícies en funció dels requisits del client, l’ ISP evita que els clients depenguin d'interfícies que no utilitzen, la qual cosa minimitza l'impacte dels canvis i promou una millor organització del codi.
Exemple:
from abc import ABC, abstractmethod
class Impressora(ABC):
@abstractmethod
def imprimir_document(self, document):
pass
class Escaner(ABC):
@abstractmethod
def escanejar_document(self):
pass
class ImpressoraMultifuncional(Impressora, Escaner):
def imprimir_document(self, document):
print(f"Imprimint: {document}")
def escanejar_document(self):
print("Escanejant document")
class ImpressoraSimple(Impressora):
def imprimir_document(self, document):
print(f"Imprimint: {document}")
# Els clients només utilitzen les interfícies que necessiten
Impressora_multifuncional = ImpressoraMultifuncional()
Impressora_multifuncional.imprimir_document("Informe")
Impressora_multifuncional.escanejar_document()
Impressora_simple = ImpressoraSimple()
Impressora_simple.imprimir_document("Informe")
El principi d'inversió de dependències(Dependency Inversion Principle) suggereix que els mòduls d'alt nivell no han de dependre dels mòduls de baix nivell, sinó que tots dos han de dependre d'abstraccions. Aquest principi promou el desacoblament i redueix la dependència entre classes en introduir interfícies o classes abstractes que defineixen el contracte entre elles, facilitant el manteniment, les proves i l'escalabilitat.
Exemple:
from abc import ABC, abstractmethod
class BaseDeDades(ABC):
@abstractmethod
def guardar(self, dades):
pass
class BaseDeDadesMySQL(BaseDeDades):
def guardar(self, dades):
print(f"Guardant {dades} a la base de dades MySQL")
class BaseDeDadesMongoDB(BaseDeDades):
def guardar(self, dades):
print(f"Guardant {dades} a la base de dades MongoDB")
class GestioDeDades:
def __init__(self, db: BaseDeDades):
self.db = db
def guardar_dades(self, dades):
self.db.guardar(dades)
# El mòdulo d'altura nivell depen de l'abstracción (interfície)
mysql_db = BaseDeDadesMySQL()
gestor = GestioDeDades(mysql_db)
gestor.guardar_dades("Algunes dades")
mongodb = BaseDeDadesMongoDB()
gestor = GestioDeDades(mongodb)
gestor.guardar_dades("Altres dades")
Encara que entendre els principis S-O-L-I-D és essencial, el seu veritable valor es manifesta quan s'implementen en la pràctica. En integrar aquests principis en el disseny de software, es pot aconseguir un codi més modular, fàcil de provar i menys propens a errors. L'aplicació del SRP assegura que cada classe tingui una responsabilitat específica i ben definida, el OCP promou el disseny que s'adapta a canvis sense necessitat d'alterar el codi existent, el LSP fomenta la coherència i la intercanviabilitat de les classes, el ISP millora la claredat de les interfícies i l'adaptació als requeriments del client, i el DIP afavoreix la flexibilitat i redueix l'acoblament entre components.
Els principis S-O-L-I-D ofereixen una guia estructurada per a dissenyar sistemes orientats a objectes que són no sols més fàcils de mantenir i estendre, sinó també més resistents a canvis i escalables a llarg termini. En interioritzar i aplicar aquests principis de manera adequada, els desenvolupadors poden millorar significativament la qualitat dels seus dissenys de software i proporcionar solucions més robustes que responguin als canviants requisits del negoci. Entendre i implementar els principis S-O-L-I-D permet als desenvolupadors construir sistemes de software que siguin més fàcils de mantenir, adaptables i escalables, garantint així la durabilitat i eficiència en els projectes de desenvolupament de software.