Principios SOLID. Interface segregation principle


Principios SOLID. Interface segregation principle

Revisamos el cuarto principio SOLID. El principio de segregación de interfaces. Una clase no debe depender de una funcionalidad que no usa.



Algo de historia

Este principio fue formulado y utilizado por primera vez por Robert C. Martin, el cual explica que si tienes una clase o interface grande, lo mas seguro es que los objetos hijo de esta no usen todas sus funciones.

Principio de Segregación de Interfaces

Una clase nunca debe ser forzada a implementar una interface que no usa, empleando métodos que no tiene por qué usar.:

Principio de Segregación de Interfaces

Este principio (PSI de ahora en adelante) expone la utilidad de las interfaces y su buen uso. Las interfaces nos ayudan a desacoplar clases entre sí manteniendo un bajo acoplamiento entre ellas.

Situación actual

Necesitamos calcular la suma de las áreas y volúmenes de cierto objetos en un proyecto en el que trabajamos y hasta el articulo anterior resolvimos que:

  • La clase Figuras es abstracta y define un método calcularArea()
  • Sus clases hijas implementan ese método
  • La clase CalculadorAreaPSL calcula las áreas de los objetos pasados
  • La clase CalculadorVolumenesPSL calcula los volúmenes de los objetos pasados y
  • CalculadorSumaPSL haría la suma pedida

Si necesitábamos calcular la suma de las áreas de un Triangulo y un Circulo, solo bastaba con entregarle un objeto CalculadorAreaPSL a la clase CalculadorSumaPSL y listo. Y con la suma de volúmenes, le pasábamos un objeto CalculadorVolumenesPSL a la clase CalculadorSumaPSL.

Implementando el PSI

Como nuestro sistema de calculo de Sumas de áreas y volúmenes funciona, solo tendríamos que hacer un pequeño cambio, eliminar el método abstracto de la clase Figuras e incluirlo en una Interface
Clase Figuras quedaría:
/* Clase Figuras */
namespace Blockpc\Clases;
abstract class Figuras {
}
Y la Interface que llamaremos IArea
/* Clase IArea */
namespace Blockpc\Clases;
Interface IArea {
public function calcularArea();
}
Luego, en cada figura toca implementar esta Interface, así la clase Circulo queda:
/* Clase Circulo */
namespace Blockpc\Clases;
class Circulo extends Figuras implements IArea {
private $radio;
private $altura;
public function __construct(int $radio, int $altura = 0) {
$this->radio = $radio;
$this->altura = $altura;
}
public function getRadio() {
return $this->radio;
}
public function getAltura() {
return $this->altura;
}
public function calcularArea() {
return pi() * pow($this->radio, 2);
}
}
En nuestro archivo index.php no deberíamos cambiar nada desde el articulo pasado y seguiría funcionando.
/* Datos de los Objetos */
$circulo = new Blockpc\Clases\Circulo(4, 2);
$cuadrado = new Blockpc\Clases\Cuadrado(4, 2);
$triangulo = new Blockpc\Clases\Triangulo(4, 3, 2);
$rectangulo = new Blockpc\Clases\Rectangulo(4, 3, 2);
/* Solo calculando las áreas */
$areas = new Blockpc\Clases\CalculadorAreaPSL($circulo, $cuadrado, $triangulo, $rectangulo);
$sumaAreas = new Blockpc\Clases\CalculadorSumaPSL($areas);
/* Solo calculando los volúmenes */
$volumenes = new Blockpc\Clases\CalculadorVolumenesPSL($circulo, $cuadrado, $triangulo, $rectangulo);
$sumaVolumenes = new Blockpc\Clases\CalculadorSumaPSL($volumenes);

Un detalle no menor

Si creáramos una clase Rombo (paralelogramo de cuatro lados iguales) y que herede de Figuras, pero que no implemente la Interface IArea podríamos hacer esto:
/* Clase Rombo */
namespace Blockpc\Clases;
class Rombo extends Figuras {
private $baseMenor;
private $baseMayor;
private $altura;
public function __construct(int $baseMenor, int $baseMayor, int $altura = 0) {
$this->baseMenor = $baseMenor;
$this->baseMayor = $baseMayor;
$this->altura = $altura;
}
public function getBaseMenor() {
return $this->baseMenor;
}
public function getBaseMayor() {
return $this->baseMayor;
}
public function getAltura() {
return $this->altura;
}
public function calcularArea() {
return ($this->baseMenor * $this->baseMayor)/2;
}
}
Como pueden ver, que no implementemos la Interface no nos impide crear el método calcularArea() y menos crear un objeto que cuente en el calculo de áreas que hace la clase madre CalculadorAreaPSL. Pueden hacer las pruebas en el archivo index.php y verán que no emite ningún error y devuelve la suma de áreas y volúmenes como se espera.
/* Agregamos un objeto Rombo */
$rombo = new Blockpc\Clases\Rombo(4, 3, 2);
/* Solo calculando las áreas */
$areas = new Blockpc\Clases\CalculadorAreaPSL($circulo, $cuadrado, $triangulo, $rectangulo, $rombo);
$sumaAreas = new Blockpc\Clases\CalculadorSumaPSL($areas);
/* Solo calculando los volúmenes */
$volumenes = new Blockpc\Clases\CalculadorVolumenesPSL($circulo, $cuadrado, $triangulo, $rectangulo, $rombo);
$sumaVolumenes = new Blockpc\Clases\CalculadorSumaPSL($volumenes);
¿Entonces para que implementar una Interface?
Pues debemos asegurar que solo las clases que implementen la Interface IArea cuenten para la suma.
¿Como lo hacemos?
Cambiando la validación que se realiza tanto en CalculadorAreaPSL como en CalculadorVolumenesPSL.
/* Validación del objeto */
/* if ( is_a($figura, 'Blockpc\Clases\Figuras') ) { */
if ( is_a($figura, 'Blockpc\Clases\IArea') ) {
$volumenes[] = $figura->calcularArea() * $figura->getAltura() ?? 0;
continue;
}
Ahora debería mostrar el error Se esperaba una figura pues Rombo siendo una clase hija de Figuras no implementa la interface IArea.
¿Podemos crear una Interface IVolumen?
Pues claro:
/* Interface IVolumen */
namespace Blockpc\Clases;
Interface IVolumen {
public function calcularVolumen();
}
Luego solo toca implementarla en cada clase hija, pero esto se los dejo a Uds como tarea.

Clases Abstractas versus Interfaces

Recordemos primero que una clase abstracta define al menos un método abstracto común a todas las clases hijas, ademas pueden definir propiedades e implementar sus métodos internos pero no se pueden crear objetos de esta.
Una Interface declara métodos abstractos que deben ser implementados en las clases que la usen, no poseen lógica y tampoco pueden ser instanciados.
Una clase puede extender solo a una clase abstracta pero puede implementar muchas interfaces según sea la necesidad.
Una clase tiene una relación es un/es una con una clase abstracta y de contrato con la Interface.
Una clase abstracta hace uso del polimorfismo.
Ambas, tanto una interface como una clase abstracta son consideradas como módulos de bajo nivel

Ultimas consideraciones

La documentación sobre los objetos en PHP la encuentras aca
La documentación sobre interfaces en PHP la encuentras aca

El código fuente del articulo lo descargas desde acá

Saludos, comenten y nos leemos en un próximo articulo.

Etiquetas solid

Ultima actualización Miércoles 09 de Mayo, 2018




Agregar Comentario