Newsletter para devsEntra

Manual de buenas prácticas: D de S.O.L.I.D. Inversión de dependencia

Parece que fuera ayer cuando comencé esta saga de buenas prácticas de programación utilizando los principios S.O.L.I.D…

¡Que recuerdos!

Como siempre, os dejo por aquí los enlaces a las otras entregas de la saga, por si queréis empaparos bien de estos principios:

¿A qúe hace referencia la D de S.O.L.I.D.? A Dependency Inversion (Inversión de dependencia).

¿Qúe nos dice este principio? Nada fácil de entender, como siempre:

El módulo de alto nivel no debe depender del módulo de bajo nivel, pero debe depender de abstracciones.

¿Cómo os quedáis?

Mi interpretación:

Si necesitáis que un objeto haga referencia a un objeto externo, para usarlo dentro del mismo, no uséis la mínima expresión del objeto, optad siempre por el uso de una abstracción.

Al final de la sección: el principio mal aplicado, podréis ver la utilidad de esta buena práctica.

Manual_buenas_practicas_principio_inversion_dependencia

La última letra de los principios viene dura como el hierro, ¡eh!

El principio de inversión de dependencia mal implementado

El ejemplo que os propongo viene con la implementación de una persistencia de datos (grabar a fichero, base de datos, etc.). Algo que a los programadores de antaño nos hace especial ilusión.

Imaginemos por un momento que necesitamos persistir el alta de un usuario y un cliente en la base de datos. Algo muy sencillito, la persistencia de email y nombre de ambos.

Empecemos por crear la clase que realiza la grabación en base de datos. La base de datos a utilizar será MySQL:

class MySQL{

    public function addUser($name, $email){
    
        //persistencia de datos
    
    }
    
    public function addClient($name, $email){
    
        //persistencia de datos
    
    }

}

Vamos a crear las clases usuario y cliente, que en sus respectivos constructores (método que se ejecuta automáticamente cuando instanciamos la clase) recibirá la clase que persiste para poder utilizarla dentro del objeto que la necesita:

class User{

    private $mySQL;
    
    public function __construct(MySQL $mySQL){
    
        $this->mySQL = $mySQL;
    
    }
    
    public function saveUser($name, $email){
    
        $this->mySQL->addUser($name, $user);
    
    }

}

class Client{

    private $mySQL;
    
    public function __construct(MySQL $mySQL){
    
        $this->mySQL = $mySQL;
    
    }
    
    public function saveClient($name, $email){
    
        $this->mySQL->addClient($name, $user);
    
    }

}

Vamos a ver cómo quedaría nuestra aplicación para poder ver la inyección de la clase claramente:

$mySQL = new MySQL();

//persistencia de usuario
$userName = 'Alberto Sánchez';
$userEmail = 'albertoS@example.com';
$user = new User($mySQL);
$user->saveUser ($userName, $userEmail);

//persistencia de cliente
$clientName = 'Captura de cangrejo azul S.A.';
$clientEmail = 'capturaC@example.com';
$client = new Client($mySQL);
$client->saveClient($clientName, $clientEmail);

Si os dais cuenta, tanto en la clase User como en la clase Client, hemos hecho fuertemente dependientes a las mismas de la clase MySQL a través de la inyección de dependencia.

Inyectar el tipo de variable (que es del tipo: una clase que hemos creado nosotros mismos o de una librería descargada) es lo que llamamos inyección de dependencias. Obligamos a la variable que inyectamos al constructor a que sea del tipo MySQL y no de cualquier otro tipo. Otra buena práctica.

Pero como siempre vamos un paso más allá:

¿Qué pasa si cambiamos el tipo de persistencia a otro distintos? Un CSV, un archivo de texto, CSV, SQLite, etc.

Pues lo que imagináis, que nos tendremos que dar una vueltecita por todas las clases que implementen persistencia a base de datos para ir cambiando la inyección del tipo MySQL a esa otra persistencia elegida.

No sólo pasa esto con las clases, sino que también lo podemos ver en el código de la aplicación.

¡No queremos que el comando reemplazar sea nuestra bandera como programadores!

El principio de inversión de dependencia correctamente implementado

Una vez analizado este código, sólo nos queda una posible solución. Un objeto que realice una abstracción de la clase de persistencia, de tal modo que lo que le inyectemos a las clases no sea tan fuertemente dependiente de la base de datos a utilizar.

En este caso nos vamos a apoyar de nuevo en los interfaces, esa utilidad que nos permite declarar un contrato al que todos los objetos que se ciñan tienen que seguir.

Lo primero que tendremos que hacer es cambiar declarar una interfaz que suba un nivel de abstracción a nuestra aplicación:

inteface Persistance{

    public function addUser($name, $email){}
    
    public function addClient($name, $email){}

}

Como ya hemos visto en alguna que otra ocasión, las interfaces no contienen lógica de programación dentro de los métodos, tan sólo contienen una declaración de los mismos.

Ahora pasamos a declarar la clase MySQL.

Como hemos hecho anteriormente, no vamos a considerar toda la lógica de conexión a base de datos, etc. ya que el propósito de este artículo es simplemente entender el concepto de la buena práctica.

class MySQL implements Persistance{

    public function addUser(name, email){
    
        //persistencia de datos
    
    }
    
    public function addClient(name, email){
    
        //persistencia de datos
    
    }

}

Hasta aquí no hemos cambiado nada. Incluso hemos añadido más código del que había.

La salsa viene ahora, este no es un artículo de ahorro de código, sino de buenas prácticas.

Veamos el código de nuestras clases de usuario y cliente:

class User{

    private $persistance;
    
    public function __construct(Persistance $persistance){
    
        $this->persistance = $persistance;
    
    }
    
    public function saveUser($name, $email){
    
        $this->persistance->addUser($name, $user);
    
    }

}

class Client{

    private $persistance;
    
    public function __construct(Persistance $persistance){
    
        $this->persistance = $persistance;
    
    }
    
    public function saveClient($name, $email){
    
        $this->persistance->addClient($name, $user);
    
    }

}

¿Os percatáis de lo que ha pasado?

Ahora no estamos obligando a la inyección de un tipo de persistencia específico, sino que inyectamos su forma genérica para así dejar cabida a todo tipo de persistencias que estén adheridas al contrato que estipula el interfaz.

Nuestro código podríamos dejarlo exactamente igual, o darle una pequeña vuelta de tuerca para sólo tener que cambiar la instanciación de la clase de persistencia en un futuro.

$persistance = new MySQL();

//persistencia de usuario
$userName = 'Alberto Sánchez';
$userEmail = 'albertoS@example.com';
$user = new User($persistance);
$user->saveUser ($userName, $userEmail);

//persistencia de cliente
$clientName = 'Captura de cangrejo azul S.A.';
$clientEmail = 'capturaC@example.com';
$client = new Client($persistance);
$client->saveClient($clientName, $clientEmail);

Esta es una de las cosas buenas de las interfaces, al obligar a tener una serie de métodos, podemos estar tranquilos de que si cambiamos el tipo de instanciación de la persistencia, el resto del código seguirá siendo exactamente igual.

De hecho, si olvidamos declarar uno de los métodos en la clase que implementa la interfaz, PHP nos lanzaría una excepción en el momento de la ejecución del código.

Os invito a ampliar más conocimientos sobre la metodología S.O.L.I.D. a través de unos ejemplos que son incluso aún más fáciles de los que yo he implementado. Lo podéis hacer a través del episodio de web reactiva sobre las buenas prácticas S.O.L.I.D..

¡Nos vemos en el siguiente fragmento de conocimiento, equipo!

Escrito originalmente por: Juan José Ramos

Imagen de Daniel Primo

Daniel Primo

CEO en pantuflas de Web Reactiva. Programador y formador en tecnologías que cambian el mundo y a las personas. @delineas en twitter y canal @webreactiva en telegram

12 recursos para developers cada domingo en tu bandeja de entrada

Además de una skill práctica bien explicada, trucos para mejorar tu futuro profesional y una pizquita de humor útil para el resto de la semana. Gratis.