La burla introduce el manejo en el código de producción

15

Asumiendo una interfaz IReader, una implementación de la interfaz IReader ReaderImplementation y una clase ReaderConsumer que consume y procesa datos del lector.

public interface IReader
{
     object Read()
}

Implementación

public class ReaderImplementation
{
    ...
    public object Read()
    {
        ...
    }
}

Consumidor:

public class ReaderConsumer()
{
    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // read some data
    public object ReadData()
    {
        IReader reader = new ReaderImplementation(this.location)
        data = reader.Read()
        ...
        return processedData    
    }
}

Para probar ReaderConsumer y el procesamiento, uso una simulación de IReader. Entonces ReaderConsumer se convierte en:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // mock constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader
    }

    // read some data
    public object ReadData()
    {
        try
        {
            if(this.reader == null)
            {
                 this.reader = new ReaderImplementation(this.location)
            }

            data = reader.Read()
            ...
            return processedData    
        }
        finally
        {
            this.reader = null
        }
    }
}

En esta solución, la burla introduce una oración if para el código de producción, ya que solo el constructor de burla proporciona una instancia de la interfaz.

Al escribir esto, me doy cuenta de que el bloque try-finally no está relacionado, ya que está ahí para manejar al usuario que cambia la ubicación durante el tiempo de ejecución de la aplicación.

En general se siente mal, ¿cómo podría manejarse mejor?

Kristian mo
fuente
16
Normalmente, esto no es un problema porque el constructor con la dependencia inyectada sería el único constructor. ¿Está fuera de discusión hacerse ReaderConsumerindependiente ReaderImplementation?
Chris Wohlert el
Actualmente sería difícil eliminar la dependencia. Al mirarlo un poco más, tengo un problema más profundo que la dependencia de ReaderImplemenatation. Dado que ReaderConsumer se crea durante el inicio de una fábrica, persiste durante la vida útil de la aplicación y acepta cambios de los usuarios, esto requiere un poco de masaje adicional. Probablemente, la entrada de configuración / usuario podría existir como un objeto y luego se podría crear ReaderConsumer y ReaderImplementation sobre la marcha. Ambas respuestas dadas resuelven bastante bien el caso más genérico.
Kristian mo
33
. Este es el punto de TDD: tener que escribir pruebas primero implica un diseño más desacoplado (de lo contrario, no podrá escribir pruebas unitarias ...). Esto ayuda a que el código sea más fácil de mantener y también extensible.
Bakuriu el
Una buena forma de detectar olores que se pueden resolver con la inyección de dependencia es buscar la palabra clave 'nuevo'. No renueves tus dependencias. Inyectarlos en su lugar.
Eternal21

Respuestas:

67

En lugar de inicializar el lector desde su método, mueva esta línea

{
    this.reader = new ReaderImplementation(this.location)
}

En el constructor sin parámetros predeterminado.

public ReaderConsumer()
{
    this.reader = new ReaderImplementation(this.location)
}

public ReaderConsumer(IReader reader)
{
    this.reader = reader
}

No existe tal cosa como un "constructor simulado", si su clase tiene una dependencia que requiere para funcionar, entonces el constructor debe recibir esa cosa o crearla.

RubberDuck
fuente
33
score ++ ... excepto desde un punto de vista DI, ese constructor predeterminado es definitivamente un olor a código.
Mathieu Guindon el
33
@ Mat'sMug no tiene nada de malo con una implementación predeterminada. La falta de encadenamiento de ctor es el olor aquí. =;) -
RubberDuck
Ah, sí , si no tiene el libro, este artículo parece describir el antipatrón de inyección bastarda bastante bien (aunque solo lo hojeé).
Mathieu Guindon el
33
@ Mat'sMug: Estás interpretando DI dogmáticamente. Si no necesita inyectar el constructor predeterminado, entonces no lo hace.
Robert Harvey
33
El libro vinculado a @ Mat'sMug también habla sobre "valores predeterminados aceptables" para que las dependencias estén bien (aunque se refiere a la inyección de propiedades). Digámoslo de esta manera: el enfoque de dos constructores cuesta más en términos de simplicidad y facilidad de mantenimiento que el enfoque de un constructor, y con la pregunta simplificada en exceso para mayor claridad, el costo no está justificado, pero puede ser en algunos casos .
Carl Leth el
54

Solo necesitas el constructor único:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader;
    }

en su código de producción:

var rc = new ReaderConsumer(new ReaderImplementation(0));

en tu prueba:

var rc = new ReaderConsumer(new MockImplementation(0));
Ewan
fuente
13

Examine la inyección de dependencia y la inversión del control

Tanto Ewan como RubberDuck tienen excelentes respuestas. Pero quería mencionar otra área para analizar, que es la inyección de dependencia (DI) y la inversión de control (IoC). Ambos enfoques mueven el problema que está experimentando a un marco / biblioteca para que no tenga que preocuparse por ello.

Su ejemplo es simple y se elimina rápidamente, pero, inevitablemente, va a construir sobre él y terminará con toneladas de constructores o rutinas de inicialización que se parecen a:

var foo = nuevo Foo (nuevo Bar (nuevo Baz (), nuevo Quz ()), nuevo Foo2 ());

Con DI / IoC, utiliza una biblioteca que le permite deletrear las reglas para hacer coincidir las interfaces con las implementaciones y luego simplemente dice "Give me a Foo" y descubre cómo conectar todo.

Hay muchos contenedores de IoC muy amigables (como se los llama), y voy a recomendar uno para mirar, pero, por favor, explore, ya que hay muchas buenas opciones.

Una simple para empezar es:

http://www.ninject.org/

Aquí hay una lista para explorar:

http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

Azul Reginald
fuente
77
Las respuestas de Ewan y RubberDuck demuestran DI. DI / IoC no se trata de una herramienta o un marco, se trata de diseñar y estructurar el código de tal manera que se invierta el control y se inyecten las dependencias. El libro de Mark Seemann hace un gran trabajo (¡increíble!) Al explicar cómo DI / IoC es completamente alcanzable sin un marco de IoC, y por qué y cuándo un marco de IoC se convierte en una buena idea (pista: cuando tiene dependencias de dependencias de dependencias de ... .) =)
Mathieu Guindon el
Además ... +1 porque Ninject es increíble, y al releer tu respuesta parece estar bien. Sin embargo, el primer párrafo se lee un poco como las respuestas de Ewan y RubberDuck no son sobre DI, que es lo que provocó mi primer comentario.
Mathieu Guindon el
1
@ Mat'sMug Definitivamente obtén tu punto. Intenté alentar al OP para que investigara DI / IoC que los otros carteles estaban utilizando (Ewan's es Po's Man DI), pero no lo mencioné. La ventaja, por supuesto, de usar un marco es que sumerge al usuario en el mundo de DI con muchos ejemplos concretos que, con suerte, los guiará a comprender lo que están haciendo ... aunque ... no siempre. :-) Y, por supuesto, me encantó el libro de Mark Seemann. Es una de mis favoritas.
Reginald Blue el
Gracias por el consejo sobre un marco DI, parece ser la forma de refactorizar este desastre correctamente :)
kristian mo