Il derisione introduce la gestione nel codice di produzione


15

Supponendo un'interfaccia IReader, un'implementazione dell'interfaccia IReader ReaderImplementation e una classe ReaderConsumer che consuma ed elabora i dati dal lettore.

public interface IReader
{
     object Read()
}

Implementazione

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

Consumatore:

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    
    }
}

Per testare ReaderConsumer e l'elaborazione uso una simulazione di IReader. Quindi ReaderConsumer diventa:

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
        }
    }
}

In questa soluzione il derisione introduce una frase if per il codice di produzione poiché solo il costruttore di simulazione fornisce un'istanza dell'interfaccia.

Durante la stesura di questo, mi rendo conto che il blocco try-finally è in qualche modo non correlato poiché è lì per gestire l'utente che modifica la posizione durante il runtime dell'applicazione.

Nel complesso sembra puzzolente, come potrebbe essere gestito meglio?


16
In genere, questo non è un problema perché il costruttore con la dipendenza iniettata sarebbe l'unico costruttore. È fuori questione diventare ReaderConsumerindipendenti ReaderImplementation?
Chris Wohlert,

Attualmente sarebbe difficile rimuovere la dipendenza. Osservandolo un po 'di più ho un problema più profondo della semplice dipendenza da ReaderImplemenatation. Poiché ReaderConsumer è stato creato durante l'avvio da una fabbrica, persiste per tutta la durata dell'applicazione e accetta modifiche da parte degli utenti, richiede un ulteriore massaggio. Probabilmente l'input utente / di configurazione potrebbe esistere come oggetto e quindi ReaderConsumer e ReaderImplementation potrebbero essere creati al volo. Entrambe le risposte fornite risolvono abbastanza bene il caso più generico.
Kristian Mo,

3
. Questo è il punto di TDD: dover scrivere i test prima implica un design più disaccoppiato (altrimenti non si è in grado di scrivere test unitari ...). Questo aiuta a rendere il codice più gestibile e anche estensibile.
Bakuriu,

Un buon modo per rilevare gli odori che possono essere risolti con Iniezione delle dipendenze è cercare la parola chiave "nuovo". Non rinnovare le tue dipendenze. Iniettali invece.
Eterno21

Risposte:


67

Invece di inizializzare il lettore dal tuo metodo, sposta questa riga

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

Nel costruttore senza parametri predefinito.

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

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

Non esiste un "costruttore finto", se la tua classe ha una dipendenza che richiede per funzionare, allora al costruttore dovrebbe essere fornita quella cosa o crearla.


3
score ++ ... tranne dal punto di vista DI, quel costruttore predefinito è sicuramente un odore di codice.
Mathieu Guindon,

3
@ Mat'sMug niente di sbagliato in un'implementazione predefinita. La mancanza di concatenamento di ctor è l'odore qui. =;) -
RubberDuck il

Oh, sì, c'è - se non hai il libro, questo articolo sembra descrivere abbastanza bene l'anti-pattern dell'iniezione bastarda (solo scremato).
Mathieu Guindon,

3
@ Mat'sMug: stai interpretando DI dogmaticamente. Se non è necessario iniettare il costruttore predefinito, non è così.
Robert Harvey,

3
Il libro @ Mat'sMug collegato parla anche di "valori predefiniti accettabili" perché le dipendenze vanno bene (anche se si riferisce all'iniezione di proprietà). Mettiamola in questo modo: l'approccio a due costruttori costa di più in termini di semplicità e manutenibilità rispetto all'approccio a un costruttore, e con la domanda troppo semplificata per chiarezza, il costo non è giustificato, ma può essere in alcuni casi .
Carl Leth,

54

Hai solo bisogno del singolo costruttore:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

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

nel tuo codice di produzione:

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

nel tuo test:

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

13

Esaminare l'iniezione di dipendenza e l'inversione del controllo

Sia Ewan che RubberDuck hanno risposte eccellenti. Ma volevo menzionare un'altra area in cui esaminare Dependency Injection (DI) e Inversion of Control (IoC). Entrambi questi approcci spostano il problema riscontrato in un framework / libreria in modo da non doversi preoccupare.

Il tuo esempio è semplice e viene rapidamente eliminato, ma inevitabilmente ti baserai su di esso e finirai con tonnellate di costruttori o routine di inizializzazione che assomigliano a:

var foo = new Foo (nuova barra (nuova Baz (), nuova Quz ()), nuova Foo2 ());

Con DI / IoC usi una libreria che ti permette di precisare le regole per abbinare le interfacce alle implementazioni e poi dici semplicemente "Give me a Foo" e scopre come collegare tutto.

Ci sono molti contenitori IoC molto amichevoli (come vengono chiamati) là fuori, e ho intenzione di raccomandarne uno da guardare, ma, per favore, esplora, poiché ci sono molte scelte eccellenti.

Uno semplice per cominciare è:

http://www.ninject.org/

Ecco un elenco da esplorare:

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


7
Le risposte di Ewan e RubberDuck dimostrano DI. DI / IoC non riguarda uno strumento o un framework, si tratta di progettare e strutturare il codice in modo tale da invertire il controllo e iniettare dipendenze. Il libro di Mark Seemann fa un ottimo lavoro (fantastico!) Spiegando come DI / IoC sia completamente realizzabile senza un framework IoC, e perché e quando un framework IoC diventa una buona idea (suggerimento: quando si hanno dipendenze di dipendenze di dipendenze di .. .) =)
Mathieu Guindon il

Inoltre ... +1 perché Ninject è fantastico, e rileggendo la tua risposta sembra soddisfacente. Il primo paragrafo è un po 'come se le risposte di Ewan e RubberDuck non riguardassero DI, il che è ciò che ha spinto il mio primo commento.
Mathieu Guindon,

1
@ Mat'sMug Sicuramente il tuo punto. Stavo cercando di incoraggiare l'OP a esaminare DI / IoC che gli altri manifesti stavano, in effetti, utilizzando (Ewan's is Poor Man's DI), ma non ne ho parlato. Il vantaggio, ovviamente, dell'uso di un framework è che immergerà l'utente nel mondo dei DI con molti esempi concreti che, si spera, li guideranno a capire cosa stanno facendo ... sebbene ... non sempre. :-) E, naturalmente, ho adorato il libro di Mark Seemann. È uno dei miei preferiti.
Reginald Blue,

Grazie per il suggerimento su un framework DI, sembra essere il modo per riformattare correttamente questo casino :)
Kristian Mo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.