L'iniezione delle dipendenze dovrebbe essere effettuata nel ctor o per metodo?


16

Ritenere:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

contro:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

Mentre l'iniezione di Ctor rende difficile l'estensione (qualsiasi codice che chiama il ctor dovrà essere aggiornato quando vengono aggiunte nuove dipendenze) e l'iniezione a livello di metodo sembra rimanere più incapsulata dalla dipendenza a livello di classe e non riesco a trovare altri argomenti a favore / contro questi approcci .

Esiste un approccio definitivo da prendere per l'iniezione?

(NB: ho cercato informazioni su questo e ho cercato di rendere oggettiva questa domanda.)


4
Se le tue classi sono coerenti, molti metodi diversi utilizzerebbero gli stessi campi. Questo dovrebbe darti la risposta che cerchi ...
Oded

@Oded Un buon punto, la coesione influenzerebbe pesantemente la decisione. Se una classe è, diciamo, raccolte di metodi di supporto che hanno ciascuna dipendenze diverse (apparentemente non coesive, ma logicamente simili) e un'altra è altamente coesiva, dovrebbero usare ciascuna l'approccio più vantaggioso. Tuttavia, ciò causerebbe incoerenza tra una soluzione.
StuperUser

Rilevante per gli esempi che ho fornito: en.wikipedia.org/wiki/False_dilemma
StuperUser

Risposte:


3

Dipende, se l'oggetto iniettato viene utilizzato da più di un metodo nella classe e iniettarlo in ciascun metodo non ha senso, è necessario risolvere le dipendenze per ogni chiamata di metodo, ma se la dipendenza viene utilizzata solo da un metodo iniettato nel costruttore non è buono, ma poiché si stanno allocando risorse che non potrebbero mai essere utilizzate.


15

Giusto, prima di tutto, affrontiamo "Qualsiasi codice che chiama il ctor dovrà essere aggiornato quando vengono aggiunte nuove dipendenze"; Per essere chiari, se stai facendo l'iniezione di dipendenza e hai del codice che chiama new () su un oggetto con dipendenze, lo stai facendo in modo sbagliato .

Il tuo contenitore DI dovrebbe essere in grado di iniettare tutte le dipendenze rilevanti, quindi non devi preoccuparti di cambiare la firma del costruttore, quindi quell'argomento non è valido.

Per quanto riguarda l'idea dell'iniezione per metodo vs. per classe, ci sono due problemi principali con l'iniezione per metodo.

Un problema è che i metodi della tua classe dovrebbero condividere le dipendenze, è un modo per assicurarsi che le tue classi siano separate in modo efficace, se vedi una classe con un gran numero di dipendenze (probabilmente più di 4-5), allora quella classe è un candidato principale per refactoring in due classi.

Il prossimo problema è che per "iniettare" le dipendenze, per metodo, dovresti passarle nella chiamata del metodo. Ciò significa che dovrai risolvere le dipendenze prima della chiamata del metodo, quindi probabilmente finirai con un mucchio di codice come questo:

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Ora, supponiamo che chiamerai questo metodo in 10 punti intorno alla tua applicazione: avrai 10 di questi frammenti. Quindi, supponiamo che tu debba aggiungere un'altra dipendenza a DoStuff (): dovrai cambiare lo snippet 10 volte (o racchiuderlo in un metodo, nel qual caso stai semplicemente replicando manualmente il comportamento DI, il che è uno spreco di tempo).

Quindi, ciò che hai praticamente fatto lì è rendere le tue classi che utilizzano DI consapevoli del proprio contenitore DI, il che è una pessima idea , in quanto porta molto rapidamente a un design ingombrante che è difficile da mantenere.

Confrontalo con l'iniezione del costruttore; Nell'iniezione del costruttore non sei legato a un particolare contenitore DI e non sei mai direttamente responsabile per soddisfare le dipendenze delle tue classi, quindi la manutenzione è abbastanza priva di mal di testa.

Mi sembra che tu stia cercando di applicare l'IoC a una classe contenente un sacco di metodi di supporto non correlati, mentre potresti essere meglio suddividere la classe di supporto in un numero di classi di servizio in base all'utilizzo, quindi utilizzare l'helper per delegare il chiamate. Questo non è ancora un ottimo approccio (l'aiutante classificato con metodi che fanno qualcosa di più complesso del semplice trattare gli argomenti che gli vengono passati sono generalmente solo classi di servizio scritte male), ma almeno manterrà il tuo design un po 'più pulito .

(NB: ho seguito l'approccio che stai suggerendo prima, ed è stata una pessima idea che non ho ripetuto da allora. Alla fine stavo cercando di separare le classi che non avevano davvero bisogno di essere separate, e alla fine con una serie di interfacce in cui ogni chiamata di metodo richiedeva una selezione quasi fissa di altre interfacce. È stato un incubo da mantenere.)


1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Se stai testando un metodo su un'unità in una classe devi istanziarlo o usi DI per creare un'istanza del tuo codice in prova?
StuperUser

@StuperUser I test delle unità sono uno scenario leggermente diverso, il mio commento si applica solo al codice di produzione. Man mano che creerai dipendenze simulate (o stub) per i tuoi test tramite un framework beffardo, ognuna con un comportamento preconfigurato e una serie di ipotesi e asserzioni, dovrai iniettarle a mano.
Ed James,

1
Questo è il codice di cui sto parlando quando dico che il "any code calling the ctor will need updating"mio codice di produzione non chiama i medici. Per queste ragioni.
StuperUser

2
@StuperUser Abbastanza giusto, ma continuerai a chiamare i metodi con le dipendenze richieste, il che significa che il resto della mia risposta è ancora valido! Ad essere sincero, sono d'accordo sul problema dell'iniezione del costruttore e della forzatura di tutte le dipendenze, dal punto di vista del test unitario. Tendo a usare l'iniezione di proprietà, in quanto ciò consente di iniettare solo le dipendenze di cui ci si aspetta di avere bisogno per un test, che trovo sostanzialmente un altro insieme di chiamate implicite Assert.WasNotCalled senza dover effettivamente scrivere alcun codice! : D
Ed James,

3

Esistono vari tipi di inversione di controllo e la tua domanda ne propone solo due (puoi anche usare factory per iniettare dipendenza).

La risposta è: dipende dalla necessità. A volte è meglio usare un tipo e un altro tipo diverso.

Personalmente mi piacciono gli iniettori di costruttore, poiché il costruttore è lì per inizializzare completamente l'oggetto. Dover chiamare un setter in seguito rende l'oggetto non completamente costruito.


3

Ti sei perso quello che almeno per me è l'iniezione di proprietà più flessibile. Uso Castle / Windsor e tutto ciò che devo fare è aggiungere una nuova proprietà automatica alla mia classe e ottengo l'oggetto iniettato senza problemi e senza interrompere alcuna interfaccia.


Sono abituato alle app Web e se queste classi si trovano nel livello Dominio, chiamato dall'interfaccia utente, che ha già risolto le sue dipendenze in modo simile a Iniezione proprietà. Mi chiedo come gestire le classi alle quali posso trasmettere gli oggetti iniettati.
StuperUser

Mi piace anche l'iniezione di proprietà, semplicemente così puoi ancora usare i normali parametri del costruttore con un contenitore DI, differenziando automaticamente tra le dipendenze risolte e non risolte. Tuttavia, poiché l'iniezione di proprietà e di proprietà sono funzionalmente identiche per questo scenario, ho scelto di non menzionarlo;)
Ed James,

L'iniezione di proprietà impone che ogni oggetto sia mutabile e crea istanze in uno stato non valido. Perché sarebbe preferibile?
Chris Pitman,

2
@Chris In realtà, in condizioni normali il costruttore e l'iniezione di proprietà agiscono in modo praticamente identico, con l'oggetto che viene completamente iniettato durante la risoluzione. L'unica volta che può essere considerato "non valido" è durante il test dell'unità, quando non si utilizzerà il contenitore DI per creare un'istanza dell'implementazione. Vedi un mio commento sulla mia risposta per la mia opinione sul perché questo è effettivamente vantaggioso. Apprezzo che la mutabilità potrebbe essere un problema, ma realisticamente farai riferimento solo alle classi risolte attraverso le loro interfacce, che non esporranno le dipendenze da modificare.
Ed James,
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.