Principios SOLID. Dependency Inversion Principle


Principios SOLID. Dependency Inversion Principle

Revisamos el ultimo de los principios SOLID. El principio de inversión de dependencias. Debemos depender de abstracciones y no de clases concretas.



Principios SOLID. Dependency Inversion Principle

Revisamos el ultimo de los principios SOLID. El principio de inversión de dependencias. Debemos depender de abstracciones y no de clases concretas.

Algo de historia


Este es el ultimo de los principios de diseño de software SOLID que fueron reunidos por Robert C. Martin (@unclebobmartin) a mediados de los noventa en un artículo de diseño orientado a objetos en C++.

Principio de Inversión de Dependencias


La definición de este principio según Robert Martin consta de dos partes.

Los módulos de alto nivel no deberían depender de los de bajo nivel, ambos deberían depender de abstracciones.

Principio de Inversión de Dependencias. Parte I
Una clase de alto nivel es aquella que crea un objeto para trabajar directamente con el usuario. En nuestro ejemplo las podemos encontrar en nuestro archivo index.php.

Las abstracciones no deben depender de los detalles, los detalles deben depender de las abstracciones.

Principio de Inversión de Dependencias. Parte II
La idea principal es que si tenemos una clase que necesite de un objeto para realizar su tarea este no sea creado en la clase que lo necesite sino mas bien que sea o una Interface o bien de una clase abstracta.

Sigamos con las figuras


En el articulo anterior aplicamos el Principio de Segregación de Interfaces a nuestros objetos.
Ahora pensemos en guardar los datos de las figuras en una base de datos como MariaDB.
Usamos la extensión PDO de PHP para realizar la conexión y almacenar los datos de las figuras.
Almacenaremos un dato en formato JSON para los objetos, las áreas y los volúmenes. Esto debido a que aprovecharemos la clase CalculadorVolumenesPSL de la cual podemos tomar todos esos datos.
En primera instancia creamos nuestra tabla figuras en la base de datos blockpc:
-- Archivo figuras.sql
START TRANSACTION;
CREATE DATABASE IF NOT EXISTS blockpc;
USE blockpc;
CREATE TABLE `figuras` (
`id` bigint(20) NOT NULL,
`figuras` text COLLATE utf8_spanish_ci NOT NULL,
`areas` text COLLATE utf8_spanish_ci NOT NULL,
`volumenes` text COLLATE utf8_spanish_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish_ci;
ALTER TABLE `figuras` ADD PRIMARY KEY (`id`);
ALTER TABLE `figuras` MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
COMMIT;
El archivo SQL ira en el código fuente.
Necesitamos crear una conexión a la base de datos, la haremos usando la extensión PDO de PHP
/* Clase Database */
namespace Blockpc\Clases;
class Database extends \PDO {
const DB_HOST = "localhost";
const DB_PORT = 3306;
const DB_NAME = "blockpc";
const DB_USER = "root"; /* tu usuario */
const DB_PASS = "root"; /* tu password */
const DB_CHAR = "utf8";
public function __construct() {
try {
$dsn = 'mysql:dbname=' . self::DB_NAME . ';host=' . self::DB_HOST;
parent::__construct($dsn, self::DB_USER, self::DB_PASS,
[
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . self::DB_CHAR,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_EMULATE_PREPARES, false,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
]
);
} catch(\PDOException $e) {
throw new \Exception("{$e->getMessage()}");
}
}
}
Notar que Database es una clase de alto nivel y que extiende de PDO una clase abstracta.
Por otro lado creamos una Interface IDatabase que usaremos para almacenar nuestros datos.
/* Interface IDatabase */
namespace Blockpc\Clases;
interface IDatabase {
public function guardar();
}

Entonces modificamos el código a nuestra clase CalculadorVolumenesPSL para que cada vez que se cree un objeto de la clase y esta sea valida, pues que almacene los datos.
/* Clase CalculadorVolumenesPSL */
namespace Blockpc\Clases;
class CalculadorVolumenesPSL extends CalculadorAreaPSL {
private $figuras;
private $database; /* código nuevo */
private $elementos; /* código nuevo */
private $volumenes; /* código nuevo */
public function __construct(...$figuras) {
parent::__construct(...$figuras);
$this->figuras = $figuras;
$this->database = new DATABASE; /* código nuevo */
$this->elementos = []; /* código nuevo */
$this->volumenes = []; /* código nuevo */
}
public function calcular() {
foreach ($this->figuras as $figura) {
/* Validación del objeto */
if ( is_a($figura, 'Blockpc\Clases\IArea') ) {
$temporal[] = get_class($figura);
$this->volumenes[] = $figura->calcularArea() * $figura->getAltura() ?? 0;
continue;
}
throw new \Exception("Se esperaba una figura");
}
$this->elementos['figuras'] = json_encode($temporal);
return $this->volumenes;
}
public function getVolumenes() {
return $this->calcular();
}
public function getSumaAreas() {
return parent::calcular();
}
public function getElementos() {
$this->elementos['areas'] = json_encode(parent::calcular());
$this->elementos['volumenes'] = json_encode($this->volumenes);
return $this->elementos;
}
/* Función para guardar los datos */
public function guardarDatos() {
/* --- Código para guardar datos en la tabla ---
* $sql = "INSERT INTO figuras SET figuras=:figuras, areas=:areas, volumenes=:volumenes;";
* $stmt = $this->database->prepare($sql);
* $stmt->execute($this->elementos);
* return $stmt->rowCount();
**/
return "<em>Acabo de guardar los datos en la tabla</em></br>";
}
}
En la función publica guardarDatos() vemos una relación directa de su clase con la clase Databaseatravez de la variable $database.
En nuestro archivo index.php agregamos la linea:
/* La variable $volumenes es un objeto de la clase CalculadorAreaPSL
* que ya habíamos creado unas lineas antes en este archivo
**/
echo $volumenes->guardarDatos();

En nuestra clase CalculadorVolumenesPSL estamos violando el primer principio sobre una única responsabilidad al permitir que esta clase realice los cálculos y ademas guarde la información en la base de datos.
Violamos el segundo principio de Open/Closed al tener que agregar código para que la clase cumpla ambas tareas.
Y terminamos violando el principio de inversión de dependencias al estar creando una instancia de Database en la clase CalculadorVolumenesPSL. La clase CalculadorVolumenesPSL es una clase de alto nivel y hace referencia a una clase de bajo nivel como lo es Database.

La solución directa


En una clase llamada CalculadorVolumenesPID pasamos un objeto Database mediante el constructor lo que cambiaría la relación directa entre ambas clases a ser una dependencia entre ambas.
/* Clase CalculadorVolumenesPID */
public function __construct(DATABASE $database, ...$figuras) {
parent::__construct(...$figuras);
$this->figuras = $figuras;
$this->database = $database;
$this->elementos = []; /* código nuevo */
$this->volumenes = []; /* código nuevo */
}
/* El resto de la clase se mantiene sin cambios */
A este tipo de relaciones se le conocen como inyección de dependencias y se cumple cada vez que un objeto es pasado, en este caso por el constructor, a una clase. El objeto no se crea en la clase, es construido fuera de ella.

Solución aplicando PID como abstracciones


Mediante la herencia y la creación de una clase RegistrarDos que extienda de Database e implemente la Interface IDatabaseDos
/* Clase RegistrarDos */
namespace Blockpc\Clases;
class RegistrarDos extends Database implements IDatabaseDos {
private $conector;
public function __construct() {
$this->conector = parent::__construct();
}
public function guardar(CalculadorVolumenesDosPID $objeto) {
return "Estoy guardando cosas desde la clase Registrar";
}
}
Debemos pasar un objeto a la función guardar() para poder almacenar los datos, ahora creamos la Interface IDatabaseDos.
/* Interface IDatabaseDos */
namespace Blockpc\Clases;
interface IDatabaseDos {
public function guardar(CalculadorVolumenesDosPID $objeto);
}
Y en el index.php tendríamos:
$database = new Blockpc\Clases\RegistrarDos;
$volumenes = new Blockpc\Clases\CalculadorVolumenesDosPID($circulo, $cuadrado, $triangulo, $rectangulo);
echo $database->guardar($volumenes);
Acá estamos pasando un objeto de CalculadorVolumenesDosPID a la clase RegistrarDos que es quien guardara los datos finalmente.

Solución aplicando PID mediante interfaces


Creamos una clase llamada RegistrarTres que extienda de Database e implemente la Interface IDatabaseTres
/* Clase RegistrarTres */
namespace Blockpc\Clases;
class RegistrarTres extends Database implements IDatabaseTres {
private $conector;
public function __construct() {
$this->conector = parent::__construct();
}
public function guardar(CalculadorVolumenesTresPID $objeto) {
return "Estoy guardando cosas desde la clase RegistrarTres";
}
}
Al método guardar() le estamos pasando un objeto de la clase CalculadorVolumenesTresPID, como define la Interface IDatabaseTres
/* Interface IDatabaseTres */
namespace Blockpc\Clases;
interface IDatabaseTres {
public function guardar(CalculadorVolumenesTresPID $objeto);
}
Y en una clase llamada CalculadorVolumenesTresPID cambiamos la función guardarDatos() de modo que reciba como parámetro un objeto de la Interface IDatabaseTres.
/* CalculadorVolumenesTresPID */
/* El resto de la clase es idéntica a CalculadorVolumenesDosPID */
/* Función para guardar los datos */
public function guardarDatos(IDatabaseTres $database) {
return $database->guardar($this); /* Le estamos pasando el objeto de la clase */
}
En nuestro archivo index.php hacemos:
$database = new Blockpc\Clases\RegistrarTres;
$volumenes = new Blockpc\Clases\CalculadorVolumenesTresPID($circulo, $cuadrado, $triangulo, $rectangulo);
echo $volumenes->guardarDatos($database);


Ultimas consideraciones


En las dos soluciones en las que implementamos el principio de inversión de dependencias se crea un objeto $database de la clase Database indirectamente a través de la clase Registrar....
Creamos un objeto $volumenes de la clase CalculadorVolumenes... que usamos para obtener las sumas de los volúmenes y las áreas de nuestras figuras.
En la primera solución PID usamos el objeto $database para guardar los datos obtenidos en el objeto $volumenes.
En la segunda solución PID el objeto $database es pasado como argumento de la función guardarDatos() de la clase CalculadorVolumenes...
En las dos soluciones PID si cambias el motor de base de datos, solo deberias cambiar la clase Database o si PDO soporta el nuevo motor, basta con cambiar la variable $dsn

¿Cual solución es mejor?
Pues debemos recordar que estos principios no son una ley y que cumplirlos va a depender mucho de como se vaya construyendo tu desarrollo.
Se puede mejorar?
Por supuesto, implementar la Interface IDatabase... en la clase Database otorgaría un poco mas de mantenibilidad al cambiar el motor de base de datos.

El código fuente del articulo lo puedes descargar desde aca.

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

Etiquetas solid

Ultima actualización Jueves 10 de Mayo, 2018




Agregar Comentario