Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Industrial Internet of Things >> Computación en la nube

SOLID: Dominando el Diseño Orientado a Objetos para Código Limpio y Mantenible

SOLID es un acrónimo mnemotécnico para diseño de clases en programación orientada a objetos. Los principios instituyen prácticas que ayudan a desarrollar buenos hábitos de programación y código mantenible.

Al considerar mantenimiento y extensibilidad de código a largo plazo, los principios SOLID enriquecen el entorno de desarrollo de código Agile. Contabilizar y optimizar dependencias de código ayuda a crear un ciclo de vida de desarrollo de software más sencillo y organizado.

SOLID: Dominando el Diseño Orientado a Objetos para Código Limpio y Mantenible

¿Qué Son los Principios SOLID?

SOLID representa un conjunto de principios para diseñar clases. Robert C. Martin (Uncle Bob) introdujo la mayoría de principios de diseño y acuñó el acrónimo.
SOLID significa:

Los principios SOLID representan una colección de mejores prácticas para diseño de software. Cada idea representa un framework de diseño, llevando a mejores hábitos de programación, diseño de código mejorado y menos errores.

SOLID: 5 Principios Explicados

La mejor forma de entender cómo funcionan los principios SOLID es a través de ejemplos. Todos los principios son complementarios y aplican a casos de uso individuales. El orden en que se aplican los principios es irrelevante, y no todos los principios son aplicables en cada situación.

SOLID: Dominando el Diseño Orientado a Objetos para Código Limpio y Mantenible

Cada sección abajo proporciona una visión general de cada principio SOLID en el lenguaje de programación Python. Las ideas generales de SOLID aplican a cualquier lenguaje orientado a objetos, como PHP, Java o C#. Generalizar las reglas las hace aplicables a enfoques de programación modernos, como microservicios.

Principio de Responsabilidad Única (SRP)

El principio de responsabilidad única (SRP) establece: "Nunca debería haber más de una razón para que una clase cambie."

Al cambiar una clase, solo deberíamos cambiar una única funcionalidad, lo que implica que cada objeto debería tener solo un trabajo.

Como ejemplo, mira la siguiente clase:

# Una clase con múltiples responsabilidades class Animal: # Constructor de propiedad def __init__(self, name): self.name = name # Representación de propiedad def __repr__(self): return f'Animal(name="{self.name}")' # Gestión de base de datos def save(animal): print(f'Saved {animal} to the database') if __name__ == '__main__': # Instanciación de propiedad a = Animal('Cat') # Guardando propiedad en base de datos Animal.save(a) 

Al hacer cualquier cambio al método save(), el cambio ocurre en la clase Animal. Al hacer cambios de propiedad, las modificaciones también ocurren en la clase Animal.

La clase tiene dos razones para cambiar y viola el principio de responsabilidad única. Aunque el código funciona como se espera, no respetar el principio de diseño hace el código más difícil de gestionar a largo plazo.

Para implementar el principio de responsabilidad única, nota que la clase ejemplo tiene dos trabajos distintos:

SOLID: Dominando el Diseño Orientado a Objetos para Código Limpio y Mantenible

Por lo tanto, la mejor forma de abordar el problema es separar el método de gestión de base de datos en una nueva clase. Por ejemplo:

# Una clase responsable de gestión de propiedades class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' # Una clase responsable de gestión de base de datos class AnimalDB: def save(self, animal): print(f'Saved {animal} to the database') if __name__ == '__main__': # Instanciación de propiedad a = Animal('Cat') # Instanciación de base de datos db = AnimalDB() # Guardando propiedad en base de datos db.save(a) 

Cambiar la clase AnimalDB no afecta la clase Animal con el principio de responsabilidad única aplicado. El código es intuitivo y fácil de modificar.

Principio Abierto/Cerrado (OCP)

El principio abierto/cerrado (OCP) establece: "Las entidades de software deberían estar abiertas para extensión pero cerradas para modificación."

Añadir funcionalidades y casos de uso al sistema no debería requerir modificar entidades existentes. La redacción parece contradictoria — añadir nuevas funcionalidades requiere cambiar código existente.

La idea es simple de entender a través del siguiente ejemplo:

class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' class Storage: def save_to_db(self, animal): print(f'Saved {animal} to the database') 

La clase Storage guarda la información de una instancia Animal a una base de datos. Añadir nuevas funcionalidades, como guardar en un archivo CSV, requiere añadir código a la clase Storage:

class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' class Storage: def save_to_db(self, animal): print(f'Saved {animal} to the database') def save_to_csv(self,animal): printf(f’Saved {animal} to the CSV file’) 

El método save_to_csv modifica una clase Storage existente para añadir la funcionalidad. Este enfoque viola el principio abierto/cerrado al cambiar un elemento existente cuando aparece una nueva funcionalidad.

El código requiere eliminar la clase Storage de propósito general y crear clases individuales para almacenar en formatos de archivo específicos.

SOLID: Dominando el Diseño Orientado a Objetos para Código Limpio y Mantenible

El siguiente código demuestra la aplicación del principio abierto/cerrado:

class DB(): def save(self, animal): print(f'Saved {animal} to the database') class CSV(): def save(self, animal): print(f'Saved {animal} to a CSV file') 

El código cumple con el principio abierto/cerrado. El código completo ahora se ve así:

class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'"{self.name}"' class DB(): def save(self, animal): print(f'Saved {animal} to the database') class CSV(): def save(self, animal): print(f'Saved {animal} to a CSV file') if __name__ == '__main__': a = Animal('Cat') db = DB() csv = CSV() db.save(a) csv.save(a) 

Extender con funcionalidades adicionales (como guardar en un archivo XML) no modifica clases existentes.

Principio de Sustitución de Liskov (LSP)

El principio de sustitución de Liskov (LSP) establece: "Las funciones que usan punteros o referencias a clases base deben poder usar objetos de clases derivadas sin saberlo."

El principio establece que una clase padre puede sustituir una clase hija sin cambios notables en funcionalidad.

Revisa el ejemplo de escritura de archivos abajo:

# Clase padre class FileHandling(): def write_db(self): return f'Handling DB' def write_csv(self): return f'Handling CSV' # Clases hijas class WriteDB(FileHandling): def write_db(self): return f'Writing to a DB' def write_csv(self): return f"Error: Can't write to CSV, wrong file type." class WriteCSV(FileHandling): def write_csv(self): return f'Writing to a CSV file' def write_db(self): return f"Error: Can't write to DB, wrong file type." if __name__ == "__main__": # Instanciación de clase padre y llamadas a funciones db = FileHandling() csv = FileHandling() print(db.write_db()) print(db.write_csv()) # Instanciaciones de clases hijas y llamadas a funciones db = WriteDB() csv = WriteCSV() print(db.write_db()) print(db.write_csv()) print(csv.write_db()) print(csv.write_csv()) 

La clase padre (FileHandling) consiste en dos métodos para escribir a una base de datos y un archivo CSV. La clase maneja ambas funciones y retorna un mensaje.

Las dos clases hijas (WriteDB y WriteCSV) heredan propiedades de la clase padre (FileHandling). Sin embargo, ambas hijas lanzan un error al intentar usar la función de escritura inapropiada, lo que viola el principio de Sustitución de Liskov ya que las funciones de sobrescritura no corresponden a las funciones padre.

SOLID: Dominando el Diseño Orientado a Objetos para Código Limpio y Mantenible

El siguiente código resuelve los problemas:

# Clase padre class FileHandling(): def write(self): return f'Handling file' # Clases hijas class WriteDB(FileHandling): def write(self): return f'Writing to a DB' class WriteCSV(FileHandling): def write(self): return f'Writing to a CSV file' if __name__ == "__main__": # Instanciación de clase padre y llamadas a funciones db = FileHandling() csv = FileHandling() print(db.write()) print(csv.write()) # Instanciaciones de clases hijas y llamadas a funciones db = WriteDB() csv = WriteCSV() print(db.write()) print(csv.write()) 

Las clases hijas corresponden correctamente a la función padre.

Principio de Segregación de Interfaces (ISP)

El principio de segregación de interfaces (ISP) establece: "Muchas interfaces específicas de cliente son mejores que una interfaz de propósito general."

En otras palabras, interfaces de interacción más extensas se dividen en unas más pequeñas. El principio asegura que las clases solo usen los métodos que necesitan, reduciendo redundancia general.

El siguiente ejemplo demuestra una interfaz de propósito general:

class Animal(): def walk(self): pass def swim(self): pass class Cat(Animal): def walk(self): print("Struts") def fly(self): raise Exception("Cats don't swim") class Duck(Animal): def walk(self): print("Waddles") def swim(self): print("Floats") 

Las clases hijas heredan de la clase padre Animal, que contiene métodos walk y fly. Aunque ambas funciones son aceptables para ciertos animales, algunos animales tienen funcionalidades redundantes.

SOLID: Dominando el Diseño Orientado a Objetos para Código Limpio y Mantenible

Para manejar la situación, divide la interfaz en secciones más pequeñas. Por ejemplo:

class Walk(): def walk(self): pass class Swim(Walk): def swim(self): pass class Cat(Walk): def walk(self): print("Struts") class Duck(Swim): def walk(self): print("Waddles") def swim(self): print("Floats") 

La clase Fly hereda de Walk, proporcionando funcionalidad adicional a clases hijas apropiadas. El ejemplo satisface el principio de segregación de interfaces.
Añadir otro animal, como un pez, requiere atomizar la interfaz aún más ya que los peces no pueden caminar.

Principio de Inversión de Dependencias (DIP)

El principio de inversión de dependencias establece: "Depende de abstracciones, no de concreciones."

El principio apunta a reducir conexiones entre clases añadiendo una capa de abstracción. Mover dependencias a abstracciones hace el código robusto.

El siguiente ejemplo demuestra dependencia de clase sin capa de abstracción:

class LatinConverter: def latin(self, name): print(f'{name} = "Felis catus"') return "Felis catus" class Converter: def start(self): converter = LatinConverter() converter.latin('Cat') if __name__ == '__main__': converter = Converter() converter.start() 

El ejemplo tiene dos clases:

El principio de inversión de dependencias requiere añadir una capa de interfaz de abstracción entre las dos clases.

SOLID: Dominando el Diseño Orientado a Objetos para Código Limpio y Mantenible

Una solución de ejemplo se ve así:

from abc import ABC class NameConverter(ABC): def convert(self,name): pass class LatinConverter(NameConverter): def convert(self, name): print('Converting using Latin API') print(f'{name} = "Felis catus"') return "Felis catus" class Converter: def __init__(self, converter: NameConverter): self.converter = converter def start(self): self.converter.convert('Cat') if __name__ == '__main__': latin = LatinConverter() converter = Converter(latin) converter.start() 

La clase Converter ahora depende de la interfaz NameConverter en lugar de depender de LatinConverter directamente. Futuras actualizaciones permiten definir conversiones de nombres usando un lenguaje y API diferente a través de la interfaz NameConverter.

¿Por Qué Existe Necesidad de Principios SOLID?

Los principios SOLID ayudan a combatir problemas de patrones de diseño. El objetivo general de los principios SOLID es reducir dependencias de código, y añadir una nueva característica o cambiar una parte del código no rompe toda la construcción.

Como resultado de aplicar principios SOLID a diseño orientado a objetos, el código se vuelve más fácil de entender, gestionar, mantener y cambiar. Como las reglas están mejor adaptadas para proyectos grandes, aplicar principios SOLID aumenta la velocidad y eficiencia general del ciclo de vida de desarrollo.

¿Siguen Siendo Relevantes los Principios SOLID?

Aunque los principios SOLID tienen más de 20 años, aún proporcionan una buena base para diseño de arquitectura de software. SOLID proporciona principios de diseño sólidos aplicables a programas y entornos modernos, no solo a programación orientada a objetos.

Los principios SOLID aplican en situaciones donde el código se escribe y modifica por personas, organizado en módulos, y contiene elementos internos o externos.

Conclusión

Los principios SOLID ayudan a proporcionar un buen framework y guía para diseño de arquitectura de software. Los ejemplos de esta guía muestran que incluso un lenguaje de tipado dinámico como Python se beneficia de aplicar los principios a diseño de código.

Luego, lee sobre los 9 principios DevOps que ayudarán a tu equipo a sacar el máximo provecho de DevOps.

Computación en la nube

  1. Dominando la Fricción: Principios Clave para Diseño y Rendimiento de Rodamientos
  2. 5 principios de diseño para interconexiones robustas en aplicaciones intensivas en datos de alta velocidad
  3. Palabra clave static en C#: Dominando variables, métodos y clases estáticas
  4. Entendiendo clases anidadas en C#: Definición, uso y herencia
  5. Dominando las plantillas de clase en C++: guía práctica
  6. Herencia en C#: Construyendo código reutilizable y mantenible
  7. Polimorfismo en C#: Enlace estático vs dinámico explicado
  8. Dominando las clases internas anónimas en Java: Definición, sintaxis y ejemplos prácticos
  9. Dominando ObjectOutputStream de Java: Serialización, Métodos y Ejemplos Prácticos
  10. Dominando Java Generics: Construyendo Código Reutilizable y Type-Safe
  11. Dominando las operaciones de archivos en Java con java.io: creación, lectura, escritura y eliminación