¿Debo usar Inyección de dependencia o fábricas estáticas?

77

Al diseñar un sistema a menudo me enfrento con el problema de tener un montón de módulos (registro, acceso a bases de datos, etc.) que están siendo utilizados por los otros módulos. La pregunta es, ¿cómo hago para proporcionar estos componentes a otros componentes? Dos respuestas parecen ser posibles la inyección de dependencia o el uso del patrón de fábrica. Sin embargo, ambos parecen estar equivocados:

  • Las fábricas dificultan las pruebas y no permiten un intercambio fácil de implementaciones. Tampoco hacen aparentes las dependencias (por ejemplo, estás examinando un método, sin tener en cuenta el hecho de que llama a un método que llama a un método que llama a un método que utiliza una base de datos).
  • La inyección de Dependecy aumenta enormemente las listas de argumentos de los constructores y mancha algunos aspectos en todo el código. La situación típica es donde los constructores de más de la mitad de las clases se ven así (....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)

Aquí hay una situación típica con la que tengo un problema: Tengo clases de excepción, que usan descripciones de error cargadas de la base de datos, usando una consulta que tiene un parámetro de configuración de idioma del usuario, que está en el objeto de sesión del usuario. Así que para crear una nueva excepción necesito una descripción, que requiere una sesión de base de datos y la sesión del usuario. Así que estoy condenado a arrastrar todos estos objetos a través de todos mis métodos en caso de que tenga que lanzar una excepción.

¿Cómo abordo este problema?

    
pregunta U Mad 11.03.2013 - 13:36

7 respuestas

72

Use la inyección de dependencia, pero siempre que las listas de argumentos de su constructor se vuelvan demasiado grandes, refástrelo utilizando un Facade Servicio . La idea es agrupar algunos de los argumentos del constructor, introduciendo una nueva abstracción.

Por ejemplo, podría introducir un nuevo tipo SessionEnvironment que encapsule un DBSessionProvider , el UserSession y el cargado Descriptions . Para saber qué abstracciones tienen más sentido, sin embargo, uno tiene que conocer los detalles de su programa.

Ya se hizo una pregunta similar aquí en SO .

    
respondido por el Doc Brown 11.03.2013 - 13:57
19
  

La inyección de dependencia aumenta enormemente las listas de argumentos de los constructores y afecta algunos aspectos de todo tu código.

A partir de eso, no parece que entiendas lo que es correcto: la idea es invertir el patrón de creación de instancias del objeto dentro de una fábrica.

Tu problema específico parece ser un problema de POO más general. ¿Por qué no pueden los objetos simplemente lanzar excepciones normales, no legibles por humanos durante su tiempo de ejecución, y luego tener algo antes del último intento / captura que atrapa esa excepción, y en ese punto usa la información de la sesión para lanzar una excepción más bonita? ?

Otro enfoque sería tener una fábrica de excepciones, que se pasa a los objetos a través de sus constructores. En lugar de lanzar una nueva excepción, la clase puede lanzar un método de fábrica (por ejemplo, throw PrettyExceptionFactory.createException(data) .

Tenga en cuenta que sus objetos, aparte de los objetos de fábrica, nunca deben usar el operador new . Las excepciones son generalmente el único caso especial, pero en su caso, ¡podrían ser una excepción!

    
respondido por el Jonathan Rich 11.03.2013 - 14:30
12

Ya ha enumerado bastante bien las desventajas del patrón de fábrica estático, pero no estoy del todo de acuerdo con las desventajas del patrón de inyección de dependencia:

La inyección de dependencia requiere que usted escriba código para que cada dependencia no sea un error, sino una característica: lo obliga a pensar si realmente necesita estas dependencias, promoviendo así el acoplamiento suelto. En tu ejemplo:

  

Aquí hay una situación típica con la que tengo un problema: tengo clases de excepción, que utilizan descripciones de errores cargadas de la base de datos, usando una consulta que tiene un parámetro de configuración de idioma del usuario, que está en el objeto de sesión del usuario. Así que para crear una nueva excepción necesito una descripción, que requiere una sesión de base de datos y la sesión del usuario. Así que estoy condenado a arrastrar todos estos objetos a través de todos mis métodos en caso de que tenga que lanzar una excepción.

No, no estás condenado. ¿Por qué es responsabilidad de la lógica empresarial localizar sus mensajes de error para una sesión de usuario en particular? ¿Qué pasaría si, en algún momento en el futuro, quisiera usar ese servicio comercial de un programa por lotes (que no tiene una sesión de usuario ...)? ¿O qué sucede si el mensaje de error no se muestra al usuario que ha iniciado sesión actualmente, sino a su supervisor (que puede preferir un idioma diferente)? O, ¿qué sucede si desea reutilizar la lógica empresarial en el cliente (que no tiene acceso a una base de datos ...)?

Claramente, la localización de los mensajes depende de quién los mire, es decir, es responsabilidad de la capa de presentación. Por lo tanto, lanzaría excepciones ordinarias del servicio de negocios, que llevan un identificador de mensaje que luego se puede buscar en el controlador de excepciones de la capa de presentación en cualquier fuente de mensajes que utilice.

De esta manera, puede eliminar 3 dependencias innecesarias (UserSession, ExceptionFactory y probablemente descripciones), haciendo que su código sea más simple y más versátil.

En general, solo usaría fábricas estáticas para las cosas a las que necesita acceso ubicuo, y está garantizado que estén disponibles en todos los entornos en los que podríamos querer ejecutar el código (como el registro). Para todo lo demás, usaría la inyección de dependencia simple y antigua.

    
respondido por el meriton 11.03.2013 - 23:34
1

Use inyección de dependencia. El uso de fábricas estáticas es un empleo del Service Locator antipattern. Vea el trabajo seminal de Martin Fowler aquí: enlace

Si los argumentos de su constructor se vuelven demasiado grandes y no está utilizando un contenedor DI por todos los medios, escriba sus propias fábricas para la creación de instancias, permitiendo que sea configurable, ya sea mediante XML o vinculando una implementación a una interfaz.

    
respondido por el Sam 11.03.2013 - 14:15
1

Yo también iría con la inyección de dependencia. Recuerde que DI no solo se realiza a través de constructores, sino también a través de los establecedores de propiedades. Por ejemplo, el registrador podría ser inyectado como una propiedad.

Además, es posible que desee usar un contenedor de IoC que pueda levantar parte de la carga para usted, por ejemplo, manteniendo los parámetros del constructor en cosas que son necesarias en el tiempo de ejecución por la lógica de su dominio (manteniendo al constructor de una manera que revele la intención de la clase y las dependencias del dominio real) y tal vez inyectar otras clases auxiliares a través de propiedades.

Un paso más allá que quizás desee dar es el Programa Orientado a Aspectos, que se implementa en muchos de los marcos principales. Esto puede permitirle interceptar (o "recomendar" que use la terminología de AspectJ) el constructor de la clase e inyectar las propiedades relevantes, tal vez se le dé un atributo especial.

    
respondido por el Tallmaris 11.03.2013 - 14:42
-1
  

Las fábricas dificultan las pruebas y no permiten un intercambio fácil de implementaciones. Tampoco hacen aparentes las dependencias (por ejemplo, estás examinando un método, sin tener en cuenta el hecho de que llama a un método que llama a un método que llama a un método que utiliza una base de datos).

No estoy del todo de acuerdo. Al menos no en general.

Fábrica simple:

public IFoo GetIFoo()
{
    return new Foo();
}

Inyección simple:

myDependencyInjector.Bind<IFoo>().To<Foo>();

Ambos fragmentos tienen el mismo propósito, configuran un enlace entre IFoo y Foo . Todo lo demás es solo sintaxis.

Cambiar Foo a ADifferentFoo requiere exactamente tanto esfuerzo en cualquiera de los ejemplos de código.
He escuchado a personas argumentar que la inyección de dependencia permite el uso de diferentes ataduras, pero se puede hacer el mismo argumento sobre la fabricación de diferentes fábricas. Elegir el enlace correcto es exactamente tan complejo como elegir la fábrica correcta.

Los métodos de fábrica te permiten, por ejemplo, use Foo en algunos lugares y ADifferentFoo en otros lugares. Algunos pueden decir que esto es bueno (útil si lo necesitas), otros pueden decir que es malo (puedes hacer un trabajo a medias para reemplazar todo). Sin embargo, no es tan difícil evitar esta ambigüedad si se adhiere a un solo método que devuelve IFoo para que siempre tenga una sola fuente. Si no quieres dispararte a ti mismo con el pie, no sostengas una pistola cargada o asegúrate de no apuntar a tu pie.

  

La inyección de dependencia aumenta enormemente las listas de argumentos de los constructores y afecta algunos aspectos de todo tu código.

Es por esto que algunas personas prefieren recuperar explícitamente las dependencias en el constructor, de esta manera:

public MyService()
{
    _myFoo = DependencyFramework.Get<IFoo>();
}

He escuchado los argumentos pro (no hay demasiada información del constructor), he escuchado los argumentos con (el uso del constructor permite más automatización DI).

Personalmente, aunque he cedido ante nuestro senior que quiere usar argumentos de constructor, he notado un problema con la lista desplegable en VS (arriba a la derecha, para explorar los métodos de la clase actual) donde los nombres de los métodos no aparecen Mira cuando una de las firmas del método es más larga que mi pantalla (= > el constructor hinchado).

A nivel técnico, no me importa de ninguna manera. Cualquiera de las dos opciones requiere aproximadamente tanto esfuerzo para escribir. Y como estás usando DI, normalmente no llamarás a un constructor manualmente. Pero el error de la interfaz de usuario de Visual Studio me hace a favor de no hinchar el argumento del constructor.

Como nota al margen, la inyección de dependencias y las fábricas no se excluyen mutuamente . He tenido casos en los que, en lugar de insertar una dependencia, he insertado una fábrica que genera dependencias (NInject por suerte le permite usar Func<IFoo> , por lo que no necesita crear una clase de fábrica real).

Los casos de uso para esto son raros pero existen.

    
respondido por el Flater 31.07.2018 - 14:41
-1

En este ejemplo ficticio, se utiliza una clase de fábrica en tiempo de ejecución para determinar qué tipo de objeto de solicitud HTTP entrante para instanciar, basado en el método de solicitud HTTP. La propia fábrica se inyecta con una instancia de un contenedor de inyección de dependencia. Esto le permite a la fábrica hacer su determinación de tiempo de ejecución y dejar que el contenedor de inyección de dependencias maneje las dependencias. Cada objeto de solicitud HTTP entrante tiene al menos cuatro dependencias (superglobales y otros objetos).

<?php
namespace TFWD\Factories;

/**
 * A class responsible for instantiating
 * InboundHttpRequest objects (PHP 7.x)
 * 
 * @author Anthony E. Rutledge
 * @version 2.0
 */
class InboundHttpRequestFactory 
{
    private const GET = 'GET';
    private const POST = 'POST';
    private const PUT = 'PUT';
    private const PATCH = 'PATCH';
    private const DELETE = 'DELETE';

    private static $di;
    private static $method;

    // public function __construct(Injector $di, Validator $httpRequestValidator)
    // {
    //    $this->di = $di;
    //    $this->method = $httpRequestValidator->getMethod();
    // }

    public static function setInjector(Injector $di)
    {
        self::$di = $di;
    }    

    public static setMethod(string $method)
    {
        self::$method = $method;
    }

    public static function getRequest()
    {
        if (self::$method == self::GET) {
            return self::$di->get('InboundGetHttpRequest');
        } elseif ((self::$method == self::POST) && empty($_FILES)) {
            return self::$di->get('InboundPostHttpRequest');
        } elseif (self::$method == self::POST) {
            return self::$di->get('InboundFilePostHttpRequest');
        } elseif (self::$method == self::PUT) {
            return self::$di->get('InboundPutHttpRequest');
        } elseif (self::$method == self::PATCH) {
            return self::$di->get('InboundPatchHttpRequest');
        } elseif (self::$method == self::DELETE) {
            return self::$di->get('InboundDeleteHttpRequest');
        } else {
            throw new \RuntimeException("Unexpected HTTP request. Invalid request.");
        }
    }
}

El código de cliente para una configuración de tipo MVC, dentro de un index.php centralizado, puede ser similar al siguiente (validaciones omitidas).

InboundHttpRequestFactory::setInjector($di);
InboundHttpRequestFactory::setMethod($httpRequestValidator->getMethod());
$di->set('InboundHttpRequest', InboundHttpRequestFactory::getRequest());
$router = $di->get('Router');  // The Router class depends on InboundHttpRequest objects.
$router->dispatch(); 

Alternativamente, puede eliminar la naturaleza estática (y la palabra clave) de la fábrica y permitir que un inyector de dependencia administre todo (por lo tanto, el constructor comentado). Sin embargo, tendrá que cambiar algunas (no las constantes) de las referencias de los miembros de la clase ( self ) a los miembros de la instancia ( $this ).

    
respondido por el Anthony Rutledge 31.07.2018 - 14:20

Lea otras preguntas en las etiquetas