Principio de Responsabilidad Única en diseño orientado a objetos (SOLID)
En un anterior post vimos una introducción a los Principios SOLID a manera meramente teórica. El día de hoy veremos todo acerca del primer principio SOLID en la lista, The Single Responsability Principle. Empecemos por dar una definición de este principio.
Definición
El principio de responsabilidad única hace rereferencia a que una clase solo debe tener un trabajo o responsabilidad. Esto visto de otra forma, significa que solo debe existir una razón para cambiar una clase. Las clases con muchas responsabilidades son difíciles de mantener.
Ejemplo
Vamos a ver un ejemplo simple pero práctico en PHP. Supongamos que tenemos la clase Square
y SquareCollection
para representar un cuadrado y una colección de cuadrados respectivamente.
class Square
{
private string $color;
private int $size;
public function __construct(string $color, int $size)
{
$this->color = $color;
$this->size = $size;
}
public function getColor(): string
{
return $this->color;
}
public function getSize(): int
{
return $this->size;
}
}
class SquareCollection
{
/**
* @var Square[]
*/
private array $squares;
public function getSquares(): array
{
return $this->squares;
}
public function addSquare(Square $square): void
{
$this->squares[] = $square;
}
}
Ahora pensemos que queremos iterar los cuadrados en orden de tamaño ascendente. Para esto, hemos recurrido al algoritmo de ordenamiento bubble sort de tal forma que nuestro código finalmente queda así:
class SquareCollection
{
/**
* @var Square[]
*/
private array $squares;
public function getSquares(): array
{
return $this->squares;
}
public function addSquare(Square $square): void
{
$this->squares[] = $square;
}
/**
* @return Square[]
*/
protected function getOrdered(): array
{
return $this->bubbleSort($this->vegetables);
}
/**
* @param Square[] $squares
* @return Square[]
*/
private function bubbleSort(array $squares): array
{
$size = count($squares);
for ($i=0; $i<$size -1; $i++) {
for ($j=0; $j<$size - 1 - $i; $j++) {
$square = $squares[$j];
$nextSquare = $squares[$j+1];
if ($nextSquare->getSize() < $square->getSize()) {
$squares[$j+1] = $square;
$squares[$j] = $nextSquare;
}
}
}
return $squares;
}
}
Aunque funciona, veamos que la clase SquareCollection
tiene más de un trabajo o lo que es lo mismo tiene varias razones para cambiar. La primera razón sería el almacenamiento de los objetos Square
, esto incluye agregar, reemplazar, eliminar, etc. La siguiente razón es la implementación del algoritmo de ordenamiento de dichos objetos, puede que el día de mañana no queramos ordenar con bubble sort, tal vez querríamos ordenar de manera descendente o realizar ajustes al algoritmo actual de búsqueda.
Delegación de trabajo
La solución a este inconveniente es por supuesto la delegación de trabajo. Separemos la implementación del algoritmo bubble sort de la colección.
class ArrayHelper
{
/**
* @param Square[] $squares
* @return Square[]
*/
public static function bubbleSort(array $squares): array
{
$size = count($squares);
for ($i=0; $i<$size -1; $i++) {
for ($j=0; $j<$size - 1 - $i; $j++) {
$square = $squares[$j];
$nextSquare = $squares[$j+1];
if ($nextSquare->getSize() < $square->getSize()) {
$squares[$j+1] = $square;
$squares[$j] = $nextSquare;
}
}
}
return $squares;
}
}
Ahora utilicemos este helper en nuestra clase de colección.
class SquareCollection
{
/**
* @var Square[]
*/
private array $squares;
public function getSquares(): array
{
return $this->squares;
}
public function addSquare(Square $square): void
{
$this->squares[] = $square;
}
/**
* @return Square[]
*/
protected function getOrdered(): array
{
return ArrayHelper::bubbleSort($this->vegetables);
}
}
De esta manera la responsabilidad ordenar mediante bubble sort la colección ha pasado a otra clase helper y no está directamente en la clase de la colección.
Aunque este código es bueno, podemos ir un poco más allá y utilizar el patrón iterador para quitar definitivamente el método getOrdered de la colección. Te invito a revisar el siguiente repositorio en GitHub con una implementación similar.