Curso Principios SOLID - 1. Principio de Responsabilidad Única

La S de SOLID representa el Principio de responsabilidad única o Single responsibility principle. Esto quiere decir que una clase solo debe tener responsabilidad sobre una parte del código que tenga una determinada funcionalidad, la cual debe estar encapsulada en su totalidad por dicha clase, es decir, que una clase debe tener solo una razón para cambiar.

Entonces, ¿qué quiere decir que una clase debe tener una única responsabilidad? Básicamente, quiere decir que un objeto debe realizar una única tarea.

¿Cómo se si estoy violando el Principio de responsabilidad única?

No existe una ciencia exacta sobre esto, pero sí que hay algunos indicativos de que una clase puede estar haciendo más cosas de las que debería. Algunas de ellas podrían ser:

  • Número de métodos públicos: los métodos públicos pueden ser llamados desde cualquier parte del código y es posible que una clase esté haciendo demasiadas cosas si tiene muchos.
  • Hay demasiadas clases importadas: tener muchas clases importadas que no se agrupan con facilidad también puede ser un indicativo de no estar aplicando correctamente la S de SOLID.
  • Es difícil escribir tests unitarios: otro indicativo puede ser que nos cueste escribir tests unitarios de una determinada clase.
  • La clase es demasiado grande: un número de líneas demasiado grande puede estar diciéndonos que la clase hace demasiadas cosas.
  • Hay varias capas de la arquitectura en una misma clase: si estás mezclando la lógica de negocio y de presentación en una misma clase, o la capa de persistencia y la de presentación, querrá decir que tu clase no tiene una única responsabilidad.

Ejemplo práctico

No hay nada como realizar un ejemplo práctico sobre este principio para ver algunos de los puntos anteriores. En este ejemplo, vamos a tener una clase Employee que va a devolver el nombre de un empleado, su edad y se va a mostrar por pantalla:

<?php
class Employee
{
    private $name;
    private $age;

    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getAge()
    {
        return $this->age;
    }

    public function output()
    {
        return "The name of the employee is {$this->name} and he has {$this->age} years old.";
    }
}

El problema de la clase Employee es que maneja tanto la lógica de negocio como la lógica de presentación de los datos. ¿Y si quisiéramos mostrar los datos en JSON, en texto plano o en HTML?

En este caso, el principio de responsabilidad única determina que en la clase Employee solo se gestionase la lógica de negocio y en otra se creara la salida en los formatos que el programador desee. Para ello, crearemos la clase EmployeeOutputter que se encargará de mostrar los datos en el formato deseado:

<?php
class Employee
{
    private $name;
    private $age;

    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getAge()
    {
        return $this->age;
    }

}

class EmployeeOutputter
{
    private $employee;
    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }

    public function toJson()
    {
        return json_encode($this->toArray());
    }

    public function toArray()
    {
        return [
            'name' => $this->employee->getName(),
            'age' => $this->employee->getAge(),
        ];
    }

    public function output()
    {
        return "The name of the employee is {$this->employee->getName()} and he is {$this->employee->getAge()} years old.";
    }

}

$employee = new Employee("Mario", 26);
$employeeOutputter = new EmployeeOutputter($employee);