El «Single Responsibility Principle», SRP (por sus siglas en inglés) puede traducirse como «Principio de Responsabilidad Única» y tiene como objetivo lograr un bajo acoplamiento y mayor cohesión de clases y módulos en nuestro código.
Acoplar, según el diccionario de la Real Academia Española es:
«Unir una pieza o cuerpo con otro de modo que ajusten exactamente.»
Es decir un controlador ContactController
que valide datos de contactos, envíe una notificación al admin del sitio web y finalmente guarde la misma información en una tabla «contactos» tiene varias piezas acopladas:
- Validación
- Notificación y
- persistencia de datos
¿Podemos decir que sus piezas se ajustan «exactamente»?
Esta pregunta nos lleva al concepto de cohesión, según la RAE:
(En física) Fuerza de atracción que mantiene unidas las moléculas de un cuerpo.
Aunque ContactController
acopla al menos 3 capas diferentes (validación, notificación, BD), estas tareas acopladas tienen alta cohesión.
Ahora imagina un controlador de registro, que valida los datos del usuario, los guarda en la base de datos y le envía un correo de confirmación al usuario. ¿Están estas tareas relacionadas? Yo creo que sí, pero:
¿Qué tal si queremos notificar al administrador de nuevos registros, y además aumentar las estadísticas de registro en nuestro sistema de Analytics?
Quizá quieras desacoplar estas tareas adicionales.
Hay otros escenarios que nos obligan a separar tareas, por ejemplo, imagina que tienes un controlador que muestra hoteles, pero los hoteles se obtienen de una API externa.
¿Es responsabilidad del controlador interactuar con dicha API? ¿Qué sucede si el API cambia?
Si colocamos toda la conexión del API directamente en nuestros controladores, un cambio en el API nos obligaría a cambiar todos estos controladores. Mientras que si extraemos la lógica a una capa extra, un cambio en el API solo tendría repercusión en dicha capa nueva.
O quizás tienes un modelo o un controlador a cargo de abrir cuentas bancarias, como el proceso es complicado y varía dependiendo del tipo de cliente, quizá quieras desacoplar este código en otra capa con una o más implementaciones.
Cómo puedes hay varios casos donde queremos desacoplar las responsabilidades de nuestras y ¡algunos en los que no!
El concepto que quizás ya has leído del SRP es: «Una clase debe tener una sola responsabilidad». Este concepto es confuso debido a que es difícil definir exactamente qué es una «responsabilidad». Por ejemplo ¿Es validar datos una responsabilidad? O ¿Es procesar los datos desde su validación a su grabación en la base de datos una responsabilidad? Es por esto que quiero que nos enfoquemos en: «bajo acoplamiento y alta cohesión» cuando hablemos del SRP.
¿Cómo saber qué responsabilidades deben separarse o mantenerse unidas?
Uncle Bob en Clean Architecture te sugiere que pienses en «cohesión». ¿Están estas tareas sumamente relacionadas o, por el contrario, pertenecen a partes separadas de la aplicación?
Pero este conocimiento llega principalmente con la práctica. Mi consejo es que comiences escribiendo un código sencillo, protegido por una o más pruebas automatizadas y solo apliques Refactorización y Patrones de Diseño cuando sea necesario.
La Refactorización solo debe realizarse cuando tu sistema esté cubierto por pruebas, para evitar dañar la funcionalidad existente (¡es mejor código estructurado que rompa SOLID, esté probado y funcione que código que cumpla con SOLID y no compile!)
Otra manera de saber cuando y cómo aplicar estos principios es conocer Patrones de Diseño, debido a que estos patrones proveen, no solo maneras de resolver problemas comunes, sino implementaciones que cumplen con SOLID y otros principios de la Programación orientada a objetos.
Para poder refactorizar con éxito y cumplir con SOLID debes conocer sobre Patrones de Diseño y cómo implementarlos.
Es por esto que he puesto mucho hincapié en estos 3 temas:
Para que puedas implementar SOLID donde y cuando haga falta, aún en proyectos viejos o de código «legacy», con la seguridad de que tu código sigue funcionando luego de aplicar los cambios.
Debajo te recomiendo 6 series premium de Styde que te permitirán separar las responsabilidades de tus clases aplicando el Principio de Responsabilidad Única con diversos patrones y técnicas que he aprendido durante 15 años de desarrollo con PHP:
Patrón Factory Method
El Patrón Factory Method define una interfaz para crear y trabajar con un objeto, permitiendo que las clases hijas decidan cuál objeto se va a crear.
Patrón Strategy
El Patrón Strategy usa polimorfismo para desarrollar sistemas más flexibles, eliminando lógica condicional y cumpliendo con varios principios SOLID.
Patrón Observer
El Patrón Observer, también conocido como «Event Listener» o «Evento Escucha» nos permite definir una relación o dependencia de uno a muchos entre un Sujeto y muchos Observadores.
Patrón Decorador
El Patrón Decorador nos permite modificar la funcionalidad de objetos concretos en tiempo de ejecución, agregando responsabilidad a objetos individuales de forma dinámica y transparente sin afectar a otros objetos de la misma clase.
Patrón Adapter y Gateway
En esta serie aprenderás a adaptar interfaces y API de terceros utilizando los Patrones Adapter, Gateway, Stub Service, así como principios SOLID.
Manejo de colecciones en Laravel
En el Curso de colecciones con Laravel aprenderás con una serie de ejemplos prácticos cómo refactorizar código al patrón Arquitectura en Pipeline. Además aseguraremos el funcionamiento de nuestro código con pruebas automatizadas escritas en PHPUnit.
Continua aprendiendo
Si disfrutaste de este tutorial, puedes unirte a nuestro listado de correos para recibir las próximas entregas:
Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.