Robert C. Martin afirma en el Principio de Inyección de Dependencias:
A. Las clases de alto nivel no deberían depender de las clases de bajo nivel. Ambas deberían depender de las abstracciones.
B. Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.
Con lo anterior en mente empecemos con un ejemplo de código que muestra una manera de pensar y programar al momento de utilizar dos clases que tienen algo en común y que se necesitan mutuamente para lograr resultados.
Lo curioso de este ejemplo es que este código te será familiar a primera vista y esta lógica de programación es aprobada por muchos profesores de universidades e incluso aceptado por muchos profesionales del área de informática, supongamos tenemos dos clases: clase A y clase B, estas dos clases deben trabajar juntas
<?php
class A {
public function doSomething(){
echo “se supone que hago algo”;
}
}
class B {
private $a;
public function __construct(){
$this->a = new A();
$this->a->doSomething();
}
}
?>
Si tenemos en cuenta los cinco principios básicos de la programación orientada a objetos que introdujo Robert C Martin , también conocidos con el acrónimo SOLID, veremos que nuestro código vulnera varios de ellos. En concreto el principio “Open/Close” y el principio de Principio de Inversión de Dependencia (Dependency inversion principle), te explico:
Tenemos una simple clase genérica B, que depende de un objeto A para funcionar. Como se puede ver en el ejemplo pareciera no haber problema alguno, pero si los hay, sobretodo derivados por el acoplamiento alto/estrecho (tight coupling), esto hace que nuestro código no sea muy flexible y que el proyecto inevitablemente se torne poco mantenible ya que la clase B está estrechamente acoplada con la clase A, de manera que no podríamos cambiar la clase A, por C, o D sin tener que modificar el código de la clase B.
Si por ejemplo comenzamos a agregar nuevos parámetros en la clase A, tendríamos que modificar cada nueva clase en la que el objeto A sea creado, un proceso tedioso en grandes aplicaciones.
La clase A se declara implícitamente, no es posible saber que existe ese objeto sin inspeccionar el código de la clase B.
El principio “Open/Close” nos dice que las entidades de software deben estar abiertas para su extensión y cerradas para su modificación, lo anterior no se cumple en nuestro ejemplo ya que las propiedades de la clase A no deberían estar dentro del scope (Alcance) de la clase B, un objeto se comunica a través de otro mediante mensajes, lo que hace cada objeto internamente no tiene por que importarle al otro.
Debido al hecho de estar instanciando la clase A dentro del constructor de la clase B, el hacer test unitarios (Unir testing) en la clase B crearía el comportamiento no deseado de tener que probar la clase A también.
El principio de Principio de Inversión de Dependencia que nos dice que la dependencia de nuestras clases debe ser de abstracciones, no de concreciones. Es decir, nuestras clases deben depender de interfaces y no de clases concretas.
La inyección de dependencias aliviana el hecho de tener que inyectar las dependencias en el constructor de la clase dependiente, la declaración de la clase sería explícita. Analicemos el siguiente código:
<?php
interface A(){
public function doSomething();
}
//Clase B dependiende de la interfaz A
class B implements A {
private $foo;
function __construct($foo){
$this->foo = $foo;
}
public function doSomething(){
return "Something";
}
}
//Clase C también dependiende de la interfaz A
class C implements A {
public function doSomething(){
return "Something";
}
}
class D {
private $a;
public function __construct(A $a){
$this->a = $a;
$this->a->doSomething();
}
}
//Inyectamos las dependencias en el constructor.
$instanceOne = new D(new B());
$instanceTwo = new D(new C("foo text"));
?>
Como se ve en este ejemplo, estamos inyectando la dependencia desde afuera, estamos pasando en el constructor de la clase todos el objeto que la clase dependiente necesita para funcionar, esto nos brinda la posibilidad de sustituir las dependencias de manera más sencilla, y lograr que el código sea más mantenible y modular.
Se observa también que la clase D actua como Factory de clases, esta clase entrega la instancia dependiendo del parámetro que reciba, pero que pasa si no recibe ningún parámetro? Podemos especificar que cree una instancia especifica tal como se muestra:
<?php
class D {
private $a;
public function __construct(A $a = NULL){
{
$this->a = is_null($a) ? new B : $a;
$this->a->doSomething();
}
}
?>
Nótese que se utiliza el typehint “A” para forzar que la clase dependiente implemente un objeto del tipo A, ahora el objeto podría reemplazarse sencillamente siempre y cuando siga la condición de ser un objeto “A” o utilice la interfaz “A”:
<?php
public function __construct(A $a){
$this->a = $a;
$this->a->doSomething();
}
?>
En este caso estamos pasando la dependencia en el constructor de la clase, esto bien podría haberse hecho con setters y getters de esta manera:
<?php
class A {
private $b;
public function setB(B $b){
$this->b = $b;
}
public function getB(){
return $this->b->doSomething();
}
}
?>
La injeccion de dependencias también puede realizarse por métodos setter tal como muestro en este ejemplo
<?php
interface PersonInterface
{
public function getClass();
}
class PersonA implements PersonInterface
{
public function __construct($param)
{
if(!isset($param)) {
die;
}
}
public function getClass()
{
return __CLASS__;
}
class Human
{
protected $Person;
public function __construct() {}
public function setPerson(PersonInterface $PersonObject)
{
$this->Person = $PersonObject;
}
public function getPerson()
{
return $this->Person;
}
}}
$PersonA = new PersonA('Param for PersonA object');
$Human = new Human;
$Human->setPerson($PersonA);
echo $Human->getPerson()->getClass();
?>
Por último se presenta un ejemplo mas particular el cual ilustra las opciones que se tienen al enviar un correo electrónico
<?php
interface mailSystem {
public function sendMail();
}
class GmailMailSystem implements mailSystem {
public function sendMail() {
echo "You have sent an email! using " . get_class($this) . "\n";
}
}
class BasicMailSystem implements mailSystem {
public function sendMail() {
echo "You have sent an email! using " . get_class($this) . "\n";
}
}
class myClass {
private $mailer;
public function __construct($mailSystem) {
$this->mailer = $mailSystem;
}
public function myMethod() {
$this->mailer->sendMail();
}
}
$myGmailMailSystem = new GmailMailSystem();
$myBasidMailSystem = new BasicMailSystem();
$myClassIntance = new myClass($myGmailMailSystem);
$myOtherClassIntance = new myClass($myBasidMailSystem);
$myClassIntance->myMethod();
$myOtherClassIntance->myMethod();
?>