Principio Open-Closed 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 segundo principio SOLID en la lista, The Open-Closed Principle. Empecemos por dar una definición de este principio.
Definición
Este principio dice que las entidades (clases, métodos o funciones) deben estar abiertas para extensión pero cerradas para modificación. Que una entidad sea abierta para extensión, significa que es posible cambiar el comportamiento de dicha entidad. Por otro lado, que sea cerrada para modificación, implica que debe ser posible cambiar el comportamiento sin modificar el código fuente original.
Dicho en otras palabras, debe ser posible cambiar el comportamiento de una clase sin que su código fuente sea modificado. Pero, no suena esto acaso contradictorio ? cómo puede ser esto posible en términos prácticos ?. Es lo que vamos a ver en el siguiente ejemplo.
Ejemplo
Vamos a imaginarnos un ejemplo muy simple pero práctico en PHP.
class MercedesBenz
{
public $speed = 0;
protected $model = '2019';
}
class Pagani
{
public $speed = 0;
protected $model = '2020';
}
Como puedes ver hemos definido dos clases llamadas MercedezBenz
y Pagani
. Ahora viene lo interesante, supongamos que queremos implementar la funcionalidad de acelerar en dichas clases. La funcionalidad de acelerar podría ser usada por un conductor (driver). Así que vamos a realizar por ahora dicha implementación dependiendo de cada auto.
class Driver
{
public function accelerate(Auto $auto)
{
if ($auto instanceof MercedezBenz)
{
$auto->speed += 10;
}
elseif ($auto instanceof Pagani)
{
$auto->speed += 15;
}
}
}
Con esto, hemos programado al conductor para que con cada auto acelere a razón de 10 Km/h y 15 Km/h según sea Mercedez Benz o Pagani respectivamente. Sin embargo, observa que cada vez que se agregue un auto nuevo, es decir, que se cree una clase nueva debe también modificarse la función accelerate
del Driver para que soporte la funcionalidad de acelerar. Es decir, la clase Driver está abierta a modificación, por lo cuál estamos violando el principio OCP. La solución a esto tiene dos caras las cuales veremos a continuación.
Polimorfismo Dinámico
La primera ténica consiste en separar el comportamiento extensible detrás de una interfaz y eliminar las dependencias. Vamos entonces a crear dicha interfaz y a implementarla en cada clase
interface AutoInterface
{
public function speedUp();
}
class MercedesBenz implements AutoInterface
{
public $speed = 0;
protected $model = '2019';
public function speedUp()
{
$this->speed += 10;
}
}
class Pagani implements AutoInterface
{
public $speed = 0;
protected $model = '2020';
public function speedUp()
{
$this->speed += 15;
}
}
Por último, vamos a mover las dependencias de la clase driver. De esta manera, la entidad conductor o driver solo debe ejecutar el método que conoce de la interfaz, es decir el método speedUp()
. Nota que con esto se elimina las dependencias de la clase Driver con cada clase de auto en específico, con lo cual se reduce el acoplamiento y se logra cumplir con el principio OCP ya que la clase Driver no necesita modificarse (cerrada a modificación) cada vez que se cree una nueva clase de auto (se extienda).
class Driver
{
public function accelerate(AutoInterface $auto)
{
$auto->speedUp();
}
}
Polimorfismo estático
La segunda ténica consiste en crear una clase abstracta o template tal que las clases hijas deban definir dicho comportamiento.
abstract class Auto
{
public $speed = 0;
abstract public function speedUp();
}
class MercedesBenz extends Auto
{
public $speed = 0;
protected $model = '2019';
public function speedUp()
{
$this->speed += 10;
}
}
class Pagani extends Auto
{
public $speed = 0;
protected $model = '2020';
public function speedUp()
{
$this->speed += 15;
}
}
Para la clase cliente resulta indiferente. Sin embargo, es de notar que ahora el tipo de parámetro no es una interfaz sino una clase abstracta.
class Driver
{
public function accelerate(Auto $auto)
{
$auto->speedUp();
}
}