SOLID - Responsabilidad Única

El Single Responsability Principle (SRP) o Principio de Responsabilidad Única estable que “una clase debe tener una, y solo una, razón para cambiar”.

En términos más simples, cada clase o módulo debería ser responsable de una parte específica de la funcionalidad, y esa responsabilidad debería estar completamente encapsulada por la clase.

¿Responsabilidad?

Al hablar de responsabilidad, hablamos de roles, de interesados en un módulo o función, actores que reclaman cambios al software.

Las responsabilidades son básicamente familias de funciones que cumplen las necesidades de dichos actores.

Cuando alguno de los roles involucrados en dichas responsabilidades decida cambiar o agregar funcionalidad, va a tener que cambiar dicha clase. Aumentando la probabilidad de colisión, complejidad y posibles bugs.

En el siguiente ejemplo tenemos una clase que afecta a varios actores, varias responsabilidades:


    class Employee {
        public function calculatePay() { … } //Contabilidad
        public function save() { … } //IT
        public function describeEmployee() { … } //Recursos Humanos
    }
                  

Por un lado contabilidad, por otro IT y por otro RRHH. Un cambio requerido por uno de ellos, va a afectar a los otros.

¿Por qué es importante entonces el Single Responsability Principle?

La desventaja de que una clase o módulo tenga más de una responsabilidad, o mejor dicho, que tenga más de una razón para cambiar, es que cuando se introduce un cambio para alguna de las responsabilidades, puede afectar el funcionamiento de otras.

En cambio, tener un código bien dividido en responsabilidades, nos trae ventajas como:

Mantenibilidad: Es más fácil mantener y modificar el código.

Flexibilidad: Puedes cambiar una parte del sistema sin afectar a otras.

Comprensibilidad: Cada parte del sistema se puede entender de manera aislada.

Síntomas de que no se cumple

Podemos identificar cuándo no estamos respetando el SRP:

  • Clase con muchas líneas de código.
  • Cada vez que hay que se hace un cambio o se agrega una funcionalidad, es necesario tocar en muchos lugares.
  • Mezcla de funcionalidades de distintas capas de arquitectura.
  • “Tocar una parte” y que se “rompa otra cosa” del mismo módulo o clase.

Ejemplo

Supongamos que tenemos una aplicación para gestionar productos en una tienda.

Una mala práctica sería tener una clase que maneje tanto las operaciones de la base de datos como la lógica de negocio de los productos.

Sin Single Responsability Principle:

En un enfoque que no sigue el Single Responsability Principle, podríamos tener una clase ProductManager que maneje tanto las operaciones de la base de datos como la lógica de negocio relacionada con los productos.


    public class ProductManager {
      public void addProduct(Product product) {
          // Lógica para agregar producto a la base de datos
      }
      
      public void calculateTotalStockValue() {
          // Lógica para calcular el valor total del inventario
      }
    }
                  

Aquí, ProductManager tiene múltiples razones para cambiar, lo que incumple el SRP.

Con Single Responsability Principle:

Vamos a separar las responsabilidades en dos clases distintas: una que maneje la interacción con la base de datos y otra que maneje los cálculos del inventario.


      public class ProductDB {
          public void addProduct(Product product) {
              // Solo maneja la lógica de la base de datos para agregar productos
          }
      }
      
      public class InventoryCalculator {
          public double calculateTotalStockValue() {
              // Solo maneja la lógica para calcular el valor del inventario
              // Retorna el valor calculado
              return 0.0;
          }
      }
                  

Clase Product para representar los productos:


      public class Product {
        // Atributos como nombre, precio, cantidad, etc.
        private String name;
        private double price;
        private int quantity;
    
        // Constructor, getters y setters
        public Product(String name, double price, int quantity) {
            this.name = name;
            this.price = price;
            this.quantity = quantity;
        }
    
        // … (otros métodos y atributos)
      }
                

Conclusiones:

Al implementar el SRP, ProductDB se ocupa exclusivamente de la interacción con la base de datos, mientras que InventoryCalculator maneja solo los cálculos relacionados con el inventario.

Esto no solo hace que el código sea más fácil de mantener y extender, sino que también ayuda a evitar errores complicados que pueden surgir cuando una clase tiene demasiadas responsabilidades.

Resumen:

Cuando se habla de una sola responsabilidad, no quiere decir que una clase tenga un sólo método, ni de un módulo que tenga una sola clase, sino que incluya toda la funcionalidad relacionada que pueda cambiar junta, que dependa de un mismo actor.

Una correcta aplicación del principio de responsabilidad única hace que nuestro código sea flexible al cambio y lo vuelve menos propenso a errores.