Cuando se crea un proyecto Python mediante programación orientada a objetos (POO), una parte importante del trabajo es planificar cómo interactuarán las diferentes clases y objetos para resolver los problemas específicos. Esta planificación se conoce como diseño orientado a objetos (OOD) y hacerlo bien puede ser un gran desafío. Si no sabe cómo diseñar las clases, los principios SOLID con Python pueden ayudarlo.
Para llevar a cabo este tutorial es necesario que tenga instalado Python en su equipo de trabajo y que tenga conocimientos básicos de programación orientada a objetos, acá dejo 2 referencias de utilidad: instalar Python en Windows, Instalar Python en Mac.
¿Qué son los principios SOLID?
En pocas palabras, los principios de diseño de Martin y Feathers nos alientan a crear software más fácil de mantener, comprensible y flexible. En consecuencia, a medida que nuestras aplicaciones crecen en tamaño, podemos reducir su complejidad y ahorrarnos muchos dolores de cabeza en el futuro. Mayor información acá.
A continuación vamos a codificar ejemplos prácticos para descubrir cómo los principios SOLID pueden conducir a un código bien organizado, flexible, mantenible y escalable. Los siguientes 5 conceptos que conforman los principios SOLID son:
- Single Responsibility
- Open/Closed
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
Principio de responsabilidad única (SRP)
El principio de responsabilidad única (SRP, por sus siglas en inglés) proviene de
Robert C. Martin , más conocido por su apodo Uncle Bob, una figura muy respetada en el mundo de la ingeniería de software y uno de los firmantes originales del Manifiesto Ágil . De hecho, él acuñó el término SOLID.
Principio de responsabilidad única significa que una clase debe tener una única responsabilidad, expresada a través de sus métodos. Si una clase se encarga de más de una tarea, entonces debe separar esas tareas en clases independientes.
Ejemplo Práctico
Veamos el siguiente caso: supongamos que estamos diseñando una aplicación para un pequeño negocio o mini market y necesitamos trabajar las siguientes funcionalidades:
- El empleado le cobra a los clientes
- El empleado también repone el stock de productos faltantes
En un pequeño negocio esto no parece extraño porque los empleados tienden a realizar varias tareas, pero que sucedería si con el tiempo el negocio crece y nos piden funcionalidades adicionales como pagar con diferentes métodos de pago, por ejemplo tarjeta de crédito y débito, además el negocio contrata gente adicional para que trabajen exclusivamente en la reposición del stock. Lo que tendríamos que hacer es agregar dichas funcionalidades a la clase Empleado y ¿cuál sería el impacto del cambio en el sistema completo?
Lo correcto considerando los nuevos requerimientos sería haber pensado desde un inicio la separación de las funcionalidades de cobro y reposición de stock para que sean ejecutadas por clases independientes, por ejemplo construir la clase Cajero y Reponedor, el Reponedor podría ser un Empleado o alguien externo a la empresa
De esta forma si en el futuro aparece un requerimiento nuevo como por ejemplo: el reponedor también podrá agregar nuevos proveedores, solo tendríamos que tocar la clase Reponedor y agregarle esa nueva responsabilidad sin comprometer el sistema por completo y sin violar el principio de responsabilidad única
Conclusión
Con este sencillo ejemplo demostramos como se viola el principio de responsabilidad única (sin enfocarnos en los detalles de implementación) y como aplicando el patrón solucionamos el problema y hacemos que nuestro diseño sea mucho más sólido y el código resultante sea más fácil de mantener, extensible, escalable y comprobable.
El concepto de responsabilidad en este contexto puede ser bastante subjetivo. Tener una única responsabilidad no significa necesariamente tener un único método. La responsabilidad no está directamente relacionada con la cantidad de métodos, sino con la tarea principal de la que es responsable tu clase en su conjunto, según tu idea de lo que representa la clase en tu código. Sin embargo, esa subjetividad no debería impedirte esforzarte por usar el SRP.
Hay que recordar que en orientación a objetos, los objetos no tienen necesariamente que corresponderse con objetos del mundo real, y que aunque en realidad exista una sola persona que se ocupe de ambas cosas, podemos crear un objeto para cada rol que desempeña. Tampoco debemos confundirnos con los ORM (Object Relational Mapping) que tienden a utilizar clases POCO para realizar mapeo entre clases y tablas de bases de datos.