Cómo usar la inyección de dependencia junto con el patrón Factory

10

Considere un módulo que es responsable de analizar archivos de cualquier tipo. Estoy pensando en usar el patrón de estrategia para abordar este problema, como ya he explicado aquí . Consulte la publicación vinculada antes de continuar con esta pregunta.

Considere la clase B que necesita el contenido del archivo product.xml. Esta clase necesitará crear una instancia del implementador concreto apropiado de la interfaz Parser para analizar el archivo XML. Puedo delegar la creación de instancias del implementador concreto apropiado a una Fábrica de tal manera que la clase B "tenga una" Fábrica. Sin embargo, la clase B "dependerá" de una Fábrica para crear instancias del implementador concreto. Esto significa que el constructor o un método de establecimiento en la clase B deberá pasar la Fábrica.

Por lo tanto, Factory y la clase B que necesita analizar un archivo estarán estrechamente unidos entre sí. Entiendo que puedo estar completamente equivocado sobre lo que he explicado hasta ahora. Me gustaría saber si puedo usar la inyección de dependencia en un escenario donde la dependencia que se inyectará es una Fábrica y cuál sería la forma correcta de implementar esto para poder aprovechar áreas como burlarse de la Fábrica en las pruebas de mi unidad.

CKing
fuente

Respuestas:

8

La forma correcta de hacerlo es depender de una interfaz y luego inyectar una implementación de esa interfaz en la Clase B.

Una interfaz es casi la cosa más delgada en la que puede confiar: lo comparo con tratar de tomar una bocanada de humo. El código tiene que acoplarse a algo o de lo contrario no hará nada, pero el acoplamiento a una interfaz es lo más desacoplado que puede obtener, sin embargo, las interfaces pueden proporcionar toda la funcionalidad que pueda desear.

Entonces, haga que el constructor de la Clase B lleve una interfaz a la clase que necesita, y haga que la fábrica produzca esa clase como implementador de la interfaz. No dependa de la fábrica, dependa de la interfaz y haga que la fábrica proporcione una implementación de esa fábrica.

Entonces, sí, usará la inyección de dependencia, pero no hay nada de malo en eso. La inyección de dependencia, particularmente la inyección de constructor simple, debería ser la forma "normal" de hacer las cosas. Simplemente posponga las cosas nuevas lo más atrás posible en su aplicación (y lo más cerca posible de la primera línea de main), y oculte la nueva llamada en una clase diseñada específicamente para crear cosas.

En pocas palabras: no dude en inyectar dependencias. Esa debería ser la forma normal de hacer las cosas.

Nick Hodges
fuente
¿La inyección del constructor pospone cosas nuevas el mayor tiempo posible?
JeffO
Estaba mal puesto: lo arreglé para decir "lo más atrás posible en su aplicación".
Nick Hodges
Si. Estaba pensando en depender de la interfaz del analizador en lugar de una fábrica. Ahora, si otra clase usa la clase B, tendrá que depender de la interfaz de la que depende la clase B. Esto continuará hasta la clase de nivel superior. Esta clase de nivel superior finalmente tendrá que usar una fábrica para crear una instancia concreta apropiada del analizador. ¿Debería la clase de nivel superior depender de una fábrica o debería usar la fábrica directamente dentro de sus métodos?
CKing
1
Bien dicho: no
dudes en
9

Creo que su premisa está un poco confundida aquí, usted habla de inyectar una fábrica, pero el patrón de fábrica es un patrón de creación cuyo propósito era hacer un subconjunto de lo que hace un marco de inyección de dependencia, cuando los marcos DI no prevalecían. útil por esa razón. Sin embargo, si tiene un marco DI, ya no necesita realmente una fábrica, ya que el marco DI puede cumplir el propósito que la fábrica habría cumplido.

Dicho esto, déjame explicarte un poco sobre la inyección de dependencia y cómo la usarías en general.

Hay una variedad de formas de hacer una inyección de dependencia, pero las más comunes son la inyección de constructor, la inyección de propiedades y el DIContainer directo. Hablaré sobre la inyección de constructores, ya que la inyección de propiedades es el enfoque incorrecto la mayoría de las veces (el enfoque correcto algunas veces), y el acceso a DIContainer no es preferible, excepto cuando absolutamente no puede hacer ninguno de los otros enfoques.

La inyección de constructor es donde tiene la interfaz para una dependencia y un DIContainer (o fábrica) que conoce la implementación concreta para esa dependencia, y cuando necesite un objeto que dependa de esa interfaz, en el momento de la construcción entrega la implementación de la fábrica a eso.

es decir

IDbConnectionProvider connProvider = DIContainer.Get<IDbConnectionProvider>();
IUserRepository userRepo = new UserRepository(connProvider);
User currentUser = userRepo.GetCurrentUser();

Muchos marcos DI pueden simplificar esto significativamente a donde su DIContainer inspeccionará el constructor de UserRepository para las interfaces para las que conoce implementaciones concretas, y las entregará automáticamente por usted; Esta técnica se llama frecuentemente Inversión de control, aunque DI e IoC son términos que se intercambian mucho y tienen diferencias vagas (si las hay).

Ahora, si se pregunta cómo el código general accede al DIContainer, bien puede tener una clase estática para acceder a él o lo que es más apropiado es que la mayoría de los marcos DI le permiten renovar un DIContainer, en el que en realidad solo se comportará como un contenedor a un diccionario singleton interno para los tipos que sabe que son concretos para interfaces dadas.

Eso significa que puede renovar el DIContainer en cualquier lugar que desee en el código y obtener efectivamente el mismo DIContainer que ya había configurado para conocer sus relaciones de interfaz a concreto. Los medios habituales para ocultar el DIContainer de partes del código que no deberían interactuar directamente con él es simplemente asegurarse de que solo los proyectos necesarios tengan una referencia al marco DI.

Jimmy Hoffa
fuente
No estoy totalmente de acuerdo con el primer párrafo. Todavía hay escenarios en los que depende de una fábrica (interfaz de implementación) que es inyectada por el contenedor DI.
Piotr Perak
Creo que se perdió el punto ya que el OP no habló sobre marcos DI o contenedores DI.
Doc Brown
@Jimmy Hoffa Si bien hizo algunos puntos muy interesantes, estoy al tanto de los contenedores IoC como Spring y el concepto de inyección de dependencia. Mi preocupación era con respecto a un escenario en el que no usa un marco de IoC en cuyo caso, debe escribir una clase que cree instancias de sus dependencias. Si la clase A depende de la interfaz B y la clase C usa la clase A, entonces la clase C depende de la interfaz B. Alguien debe darle a la Clase C una Clase B ce. ¿Debería la clase C depender de cla
CKing
Si la clase A depende de la interfaz B y la clase C usa la clase A, entonces la clase C depende de la interfaz B. Alguien necesita darle a la Clase C una interfaz B. ¿Debería la clase C depender de la interfaz B o debería depender de la clase que creó la instancia? Es decir, dependencias de la fábrica. Parece que has respondido esta pregunta en tu respuesta pero con una sobrecarga de información :)
CKing
@bot como dije en el prefacio, en gran parte puedes tratar a las fábricas como un subconjunto de la funcionalidad de un contenedor DI, no es cierto en todos los escenarios como mencionó Doc Brown, pero mira mi muestra de código y reemplaza DIContainercon DbConnectionFactoryy el concepto todavía se mantiene que recuperaría la implementación concreta de su DI / Fábrica / etc. y la entregaría a los consumidores del tipo en el momento de la construcción.
Jimmy Hoffa
5

Puede pasar una fábrica a través de la inyección de dependencia al igual que pasa cualquier otra cosa, no permita que la recursividad de la situación lo confunda. No sé qué más decir sobre implementarlo: ya sabes cómo hacer una inyección de dependencia.

Yo uso DI para inyectar fábricas con bastante regularidad.

psr
fuente
1
Si una clase depende de una Fábrica, deberá burlarse de la fábrica cuando la unidad pruebe esa clase. ¿Cómo haces para hacer eso? ¿Dejar que la clase dependa de una interfaz de fábrica?
CKing
4

No hay nada de malo en inyectar fábricas. Es una forma estándar si no puede decidir durante la compilación del objeto 'padre' qué tipo de dependencia necesitará. Creo que el ejemplo lo explicará mejor. Usaré C # porque no sé Java.


class Parent
{
    private IParserFactory parserFactory;
    public Parent(IParserFactory factory)
    {
        parserFactory = factory
    }

    public ParsedObject ParseFrom(string filename, FileType fileType)
    {
        var parser = parserFactory.CreateFor(fileType);
        return parser.Parse(filename);
    }
}
Piotr Perak
fuente