S-O-L-I-D es un conjunto de principios de diseño orientado a objetos que busca mejorar la calidad y la flexibilidad del software. El acrónimo SOLID representa cinco principios fundamentales que ayudan a los desarrolladores a crear sistemas de software más robustos, mantenibles y escalables. Estos principios son:
S - Single Responsibility Principle: Cada clase debe tener una única responsabilidad o razón para cambiar.
O - Open/Closed Principle: El software debe estar abierto para su extensión, pero cerrado para su modificación.
L - Liskov Substitution Principle: Los objetos de una clase derivada deben poder sustituirse por objetos de la clase base sin alterar el funcionamiento del programa.
I - Interface Segregation Principle: Los clientes no deben estar obligados a depender de interfaces que no utilizan.
D - Dependency Inversion Principle: Las dependencias deben estar en abstracción, no en detalles concretos.
En este blog, conocerás cada principio del enfoque S-O-L-I-D para entender sus fundamentos y cómo aplicarlos en el diseño orientado a objetos.
El principio de responsabilidad única(Single Responsibility Principle) establece que una clase debe tener solo una razón para cambiar, lo que significa que debe tener solo una tarea o responsabilidad dentro del sistema de software. Al centrar cada clase en una única preocupación, el principio de responsabilidad única reduce la complejidad, mejora la legibilidad y hace que el código sea más fácil de mantener y ampliar.
Ejemplo:
class Factura:
def __init__(self, cantidad):
self.cantidad = cantidad
def imprimir_factura(self):
# Responsabilidad: Imprimir factura
print(f"Cantidad de la factura: {self.cantidad}")
class PersistenciaFactura:
def guardar_en_archivo(self, factura):
# Responsabilidad: Guardar factura en un archivo
with open('factura.txt', 'w') as f:
f.write(f"Cantidad de la factura: {factura.cantidad}")
# Separar responsabilidades en diferentes clases
factura = Factura(100)
factura.imprimir_factura()
persistencia = PersistenciaFactura()
persistencia.guardar_en_archivo(factura)
El principio abierto/cerrado(Open/Closed Principle) enfatiza que las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas a la extensión pero cerradas a la modificación. Este principio alienta a los desarrolladores a diseñar sistemas de una manera que permita agregar nuevas funcionalidades a través de la extensión en lugar de cambiar el código existente, minimizando así el riesgo de introducir errores en el código que ya funciona.
Ejemplo:
from abc import ABC, abstractmethod
class Figura(ABC):
@abstractmethod
def area(self):
pass
class Rectangulo(Figura):
def __init__(self, ancho, alto):
self.ancho = ancho
self.alto = alto
def area(self):
return self.ancho * self.alto
class Circulo(Figura):
def __init__(self, radio):
self.radio = radio
def area(self):
return 3.14 * self.radio * self.radio
def calcular_area_total(figuras):
total = 0
for figura in figuras:
total += figura.area()
return total
figuras = [Rectangulo(10, 20), Circulo(5)]
print(calcular_area_total(figuras))
El principio de sustitución de Liskov(Liskov Substitution Principle) define que los objetos de una superclase deben poder sustituirse por objetos de sus subclases sin afectar la corrección del programa. En otras palabras, las clases derivadas deben poder sustituir a sus clases base sin alterar las propiedades deseables del programa, lo que garantiza la coherencia en el comportamiento y el diseño.
Ejemplo:
class Ave:
def volar(self):
print("Volando")
class Gorrión(Ave):
def volar(self):
print("Gorrión volando")
class Avestruz(Ave):
def volar(self):
raise Exception("El avestruz no puede volar")
def hacer_volar(ave: Ave):
ave.volar()
gorrion = Gorrión()
hacer_volar(gorrion)
avestruz = Avestruz()
# hacer_volar(avestruz) # Esto provocará una excepción, violando el LSP
El principio de segregación de interfaces(Interface Segregation Principle) aboga por diseñar interfaces específicas para cada cliente y de granularidad fina en lugar de una única interfaz grande que atienda a múltiples clientes. Al segregar las interfaces en función de los requisitos del cliente, el ISP evita que los clientes dependan de interfaces que no utilizan, lo que minimiza el impacto de los cambios y promueve una mejor organización del código.
Ejemplo:
from abc import ABC, abstractmethod
class Impresora(ABC):
@abstractmethod
def imprimir_documento(self, documento):
pass
class Escaner(ABC):
@abstractmethod
def escanear_documento(self):
pass
class ImpresoraMultifuncional(Impresora, Escaner):
def imprimir_documento(self, documento):
print(f"Imprimiendo: {documento}")
def escanear_documento(self):
print("Escaneando documento")
class ImpresoraSimple(Impresora):
def imprimir_documento(self, documento):
print(f"Imprimiendo: {documento}")
# Los clientes usan solo las interfaces que necesitan
impresora_multifuncional = ImpresoraMultifuncional()
impresora_multifuncional.imprimir_documento("Informe")
impresora_multifuncional.escanear_documento()
impresora_simple = ImpresoraSimple()
impresora_simple.imprimir_documento("Informe")
El principio de inversión de dependencias(Dependency Inversion Principle) sugiere que los módulos de alto nivel no deben depender de los módulos de bajo nivel, sino que ambos deben depender de abstracciones. Este principio promueve el desacoplamiento y reduce la dependencia entre clases al introducir interfaces o clases abstractas que definen el contrato entre ellas, facilitando el mantenimiento, las pruebas y la escalabilidad.
Ejemplo:
from abc import ABC, abstractmethod
class BaseDeDatos(ABC):
@abstractmethod
def guardar(self, datos):
pass
class BaseDeDatosMySQL(BaseDeDatos):
def guardar(self, datos):
print(f"Guardando {datos} en la base de datos MySQL")
class BaseDeDatosMongoDB(BaseDeDatos):
def guardar(self, datos):
print(f"Guardando {datos} en la base de datos MongoDB")
class ManejoDeDatos:
def __init__(self, db: BaseDeDatos):
self.db = db
def guardar_datos(self, datos):
self.db.guardar(datos)
# El módulo de alto nivel depende de la abstracción (interfaz)
mysql_db = BaseDeDatosMySQL()
manejador = ManejoDeDatos(mysql_db)
manejador.guardar_datos("Algunos datos")
mongodb = BaseDeDatosMongoDB()
manejador = ManejoDeDatos(mongodb)
manejador.guardar_datos("Otros datos")
Aunque entender los principios S-O-L-I-D es esencial, su verdadero valor se manifiesta cuando se implementan en la práctica. Al integrar estos principios en el diseño de software, se puede conseguir un código más modular, fácil de probar y menos propenso a errores. La aplicación del SRP asegura que cada clase tenga una responsabilidad específica y bien definida, el OCP promueve el diseño que se adapta a cambios sin necesidad de alterar el código existente, el LSP fomenta la coherencia y la intercambiabilidad de las clases, el ISP mejora la claridad de las interfaces y la adaptación a los requerimientos del cliente, y el DIP favorece la flexibilidad y reduce el acoplamiento entre componentes.
Los principios S-O-L-I-D ofrecen una guía estructurada para diseñar sistemas orientados a objetos que son no solo más fáciles de mantener y extender, sino también más resistentes a cambios y escalables a largo plazo. Al interiorizar y aplicar estos principios de manera adecuada, los desarrolladores pueden mejorar significativamente la calidad de sus diseños de software y proporcionar soluciones más robustas que respondan a los cambiantes requisitos del negocio. Entender e implementar los principios S-O-L-I-D permite a los desarrolladores construir sistemas de software que sean más fáciles de mantener, adaptables y escalables, garantizando así la durabilidad y eficiencia en los proyectos de desarrollo de software.