Buscar..


Introducción

La inyección de dependencia (DI) es un término elegante para "pasar cosas" . Todo lo que realmente significa es pasar las dependencias de un objeto a través del constructor y / o configuradores en lugar de crearlos en la creación del objeto dentro del objeto. La inyección de dependencia también puede referirse a los contenedores de inyección de dependencia que automatizan la construcción y la inyección.

Inyección Constructor

Los objetos a menudo dependerán de otros objetos. En lugar de crear la dependencia en el constructor, la dependencia debe pasarse al constructor como un parámetro. Esto garantiza que no haya un acoplamiento estrecho entre los objetos y permite cambiar la dependencia de la creación de instancias de clase. Esto tiene una serie de ventajas, que incluyen facilitar la lectura del código al hacer explícitas las dependencias, así como simplificar las pruebas, ya que las dependencias se pueden cambiar y simular con mayor facilidad.

En el siguiente ejemplo, el Component dependerá de una instancia de Logger , pero no crea una. Requiere que se pase uno como argumento al constructor.

interface Logger {
    public function log(string $message);
}

class Component {
    private $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }
}

Sin la inyección de dependencia, el código probablemente sería similar a:

class Component {
    private $logger;

    public function __construct() {
        $this->logger = new FooLogger();
    }
}

El uso de new para crear nuevos objetos en el constructor indica que la inyección de dependencia no se usó (o se usó de manera incompleta), y que el código está estrechamente acoplado. También es una señal de que el código no está completamente probado o puede tener pruebas frágiles que hacen suposiciones incorrectas sobre el estado del programa.

En el ejemplo anterior, donde en cambio estamos usando la inyección de dependencia, podríamos cambiar fácilmente a un registrador diferente si fuera necesario. Por ejemplo, podríamos usar una implementación del registrador que se registra en una ubicación diferente, o que utiliza un formato de registro diferente, o que se registra en la base de datos en lugar de en un archivo.

Inyección de Setter

Las dependencias también pueden ser inyectadas por setters.

interface Logger {
    public function log($message);
}

class Component {
    private $logger;
    private $databaseConnection;

    public function __construct(DatabaseConnection $databaseConnection) {
        $this->databaseConnection = $databaseConnection;
    }

    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }

    public function core() {
        $this->logSave();    
        return $this->databaseConnection->save($this);
    }

    public function logSave() {
         if ($this->logger) {
            $this->logger->log('saving');
        }
    }
}

Esto es especialmente interesante cuando la funcionalidad principal de la clase no depende de la dependencia para trabajar.

Aquí, la única dependencia necesaria es DatabaseConnection por lo que está en el constructor. La dependencia del Logger es opcional y, por lo tanto, no necesita ser parte del constructor, lo que hace que la clase sea más fácil de usar.

Tenga en cuenta que al utilizar la inyección de setter, es mejor ampliar la funcionalidad en lugar de reemplazarla. Al establecer una dependencia, no hay nada que confirme que la dependencia no cambie en algún momento, lo que podría generar resultados inesperados. Por ejemplo, un FileLogger se puede configurar al principio, y luego se puede configurar un MailLogger . Esto rompe la encapsulación y hace que los registros sean difíciles de encontrar, porque estamos reemplazando la dependencia.

Para evitar esto, deberíamos agregar una dependencia con la inyección de setter, así:

interface Logger {
    public function log($message);
}

class Component {
    private $loggers = array();
    private $databaseConnection;

    public function __construct(DatabaseConnection $databaseConnection) {
        $this->databaseConnection = $databaseConnection;
    }

    public function addLogger(Logger $logger) {
        $this->loggers[] = $logger;
    }

    public function core() {
        $this->logSave();
        return $this->databaseConnection->save($this);
    }

    public function logSave() {
        foreach ($this->loggers as $logger) {
            $logger->log('saving');
        }
    }
}

De esta forma, siempre que usemos la funcionalidad principal, no se interrumpirá incluso si no se agrega la dependencia del registrador, y cualquier registrador agregado se usará aunque se haya agregado otro registrador. Estamos extendiendo la funcionalidad en lugar de reemplazarla .

Inyección de contenedores

La inyección de dependencia (DI) en el contexto del uso de un recipiente de inyección de dependencia (DIC) se puede ver como un superconjunto de la inyección del constructor. Un DIC típicamente analizará las sugerencias de tipo de un constructor de clase y resolverá sus necesidades, inyectando efectivamente las dependencias necesarias para la ejecución de la instancia.

La implementación exacta va más allá del alcance de este documento, pero en el fondo, un DIC se basa en el uso de la firma de una clase ...

namespace Documentation;

class Example
{
    private $meaning;

    public function __construct(Meaning $meaning)
    {
        $this->meaning = $meaning;
    }
}

... para crear instancias automáticamente, confiando la mayor parte del tiempo en un sistema de carga automática .

// older PHP versions
$container->make('Documentation\Example');

// since PHP 5.5
$container->make(\Documentation\Example::class);

Si está utilizando PHP en la versión al menos 5.5 y desea obtener el nombre de una clase de la manera que se muestra arriba, la segunda es la forma correcta. De esa manera, puede encontrar rápidamente los usos de la clase utilizando los IDE modernos, que le ayudarán enormemente con la refactorización potencial. Usted no quiere confiar en cadenas regulares.

En este caso, Documentation\Example sabe que necesita un Meaning , y un DIC a su vez creará un tipo de Meaning . La implementación concreta no necesita depender de la instancia consumidora.

En su lugar, establecemos reglas en el contenedor, antes de la creación del objeto, que indica cómo se deben crear instancias de tipos específicos si es necesario.

Esto tiene una serie de ventajas, ya que un DIC puede

  • Comparte instancias comunes
  • Proporcionar una fábrica para resolver una firma de tipo.
  • Resolver una firma de interfaz

Si definimos reglas sobre cómo debe administrarse un tipo específico, podemos lograr un control preciso sobre qué tipos se comparten, se crean instancias o se crean a partir de una fábrica.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow