Iniezione di dipendenza attraverso costruttori o setter di proprietà?


151

Sto rifattorizzando una classe e aggiungendo una nuova dipendenza ad essa. La classe sta attualmente prendendo le sue dipendenze esistenti nel costruttore. Quindi, per coerenza, aggiungo il parametro al costruttore.
Certo, ci sono alcune sottoclassi più ancora di più per i test unitari, quindi ora sto giocando per alterare tutti i costruttori affinché corrispondano, e ci vogliono anni.
Mi fa pensare che l'uso delle proprietà con i setter sia un modo migliore per ottenere dipendenze. Non credo che le dipendenze iniettate dovrebbero far parte dell'interfaccia per la costruzione di un'istanza di una classe. Aggiungete una dipendenza e ora tutti i vostri utenti (sottoclassi e chiunque vi istanzia direttamente) improvvisamente lo sanno. Sembra una rottura dell'incapsulamento.

Questo non sembra essere il modello con il codice esistente qui, quindi sto cercando di scoprire qual è il consenso generale, i pro ei contro dei costruttori rispetto alle proprietà. L'uso dei setter di proprietà è migliore?

Risposte:


126

Beh, dipende :-).

Se la classe non può svolgere il proprio lavoro senza la dipendenza, quindi aggiungerla al costruttore. La classe ha bisogno della nuova dipendenza, quindi vuoi che il tuo cambiamento rompa le cose. Inoltre, la creazione di una classe non completamente inizializzata ("costruzione in due fasi") è un anti-pattern (IMHO).

Se la classe può funzionare senza la dipendenza, un setter va bene.


11
Penso che in molti casi sia preferibile utilizzare il modello Null Object e attenersi alla richiesta dei riferimenti sul costruttore. Questo evita tutti i controlli nulli e la maggiore complessità ciclomatica.
Mark Lindell,

3
@Mark: buon punto. Tuttavia, la domanda riguardava l'aggiunta di una dipendenza a una classe esistente. Quindi mantenere un costruttore no-arg consente la retrocompatibilità.
sleske,

Che dire di quando la dipendenza è necessaria per funzionare, ma un'iniezione predefinita di tale dipendenza sarà di solito sufficiente. Quindi tale dipendenza dovrebbe essere "sostituibile" dalla proprietà o da un sovraccarico del costruttore?
Patrick Szalapski,

@Patrick: Con "la classe non può fare il suo lavoro senza la dipendenza", intendevo dire che non esiste un valore di default ragionevole (ad esempio, la classe richiede una connessione DB). Nella tua situazione, entrambi funzionerebbero. Di solito opterei per l'approccio del costruttore, perché riduce la complessità (e se, ad esempio, il setter viene chiamato due volte)?
sleske


21

Gli utenti di una classe dovrebbero conoscere le dipendenze di una determinata classe. Se avessi una classe che, ad esempio, si connettesse a un database e non fornisse un mezzo per iniettare una dipendenza del livello di persistenza, un utente non saprebbe mai che dovrebbe essere disponibile una connessione al database. Tuttavia, se modifico il costruttore, faccio sapere agli utenti che esiste una dipendenza dal livello di persistenza.

Inoltre, per impedire a te stesso di dover modificare ogni uso del vecchio costruttore, applica semplicemente il concatenamento del costruttore come un ponte temporaneo tra il vecchio e il nuovo costruttore.

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

Uno dei punti dell'iniezione di dipendenza è rivelare quali dipendenze ha la classe. Se la classe ha troppe dipendenze, potrebbe essere il momento che avvenga un refactoring: ogni metodo della classe usa tutte le dipendenze? In caso contrario, questo è un buon punto di partenza per vedere dove la classe potrebbe essere divisa.


Naturalmente il concatenamento del costruttore funziona solo se esiste un valore predefinito ragionevole per il nuovo parametro. Ma per il resto non puoi evitare di rompere le cose comunque ...
sleske,

Di solito, si utilizza come parametro predefinito qualsiasi cosa si stia utilizzando nel metodo prima dell'iniezione di dipendenza. Idealmente, questo renderebbe l'aggiunta del nuovo costruttore un refactoring pulito, poiché il comportamento della classe non cambierebbe.
Doctor Blue,

1
Prendo in considerazione la gestione delle dipendenze come le connessioni al database. Penso che il problema nel mio caso sia che la classe a cui sto aggiungendo una dipendenza abbia diverse sottoclassi. In un mondo di container IOC in cui la proprietà verrebbe impostata dal container, l'uso del setter allevierebbe almeno la pressione sulla duplicazione dell'interfaccia del costruttore tra tutte le sottoclassi.
Niall Connaughton,

17

Naturalmente, indossare il costruttore significa che puoi validare tutto in una volta. Se assegni le cose in campi di sola lettura, hai delle garanzie sulle dipendenze del tuo oggetto fin dai tempi di costruzione.

È un vero dolore aggiungere nuove dipendenze, ma almeno in questo modo il compilatore continua a lamentarsi fino a quando non è corretto. È una buona cosa, penso.


Più uno per questo. Inoltre, questo riduce
notevolmente

11

Se hai un gran numero di dipendenze opzionali (che è già un odore), probabilmente l'iniezione setter è la strada da percorrere. L'iniezione del costruttore rivela meglio le tue dipendenze.


11

L'approccio preferito generale è quello di utilizzare l'iniezione del costruttore il più possibile.

L'iniezione del costruttore indica esattamente quali sono le dipendenze richieste per il corretto funzionamento dell'oggetto: nulla è più fastidioso del rinnovare un oggetto e farlo arrestare in modo anomalo quando si chiama un metodo su di esso perché alcune dipendenze non sono impostate. L'oggetto restituito da un costruttore dovrebbe essere in uno stato funzionante.

Cerca di avere un solo costruttore, mantiene il design semplice ed evita ambiguità (se non per gli umani, per il contenitore DI).

È possibile utilizzare l'iniezione di proprietà quando si ha ciò che Mark Seemann definisce un valore predefinito locale nel suo libro "Iniezione delle dipendenze in .NET": la dipendenza è facoltativa perché è possibile fornire un'implementazione funzionante ma si desidera consentire al chiamante di specificarne una diversa se necessario.

(Precedente risposta di seguito)


Penso che l'iniezione del costruttore sia migliore se l'iniezione è obbligatoria. Se questo aggiunge troppi costruttori, considera l'utilizzo di fabbriche invece di costruttori.

L'iniezione del setter è buona se l'iniezione è facoltativa o se si desidera cambiarla a metà. In genere non mi piacciono i setter, ma è una questione di gusti.


Direi che cambiare l'iniezione a metà strada è di cattivo stile (perché stai aggiungendo uno stato nascosto al tuo oggetto). Ma nessuna regola senza eccezione ovviamente ...
sleske,

Sì, questo è il motivo per cui ho detto che non mi piaceva troppo il setter ... Mi piace l'approccio del costruttore perché non può essere modificato.
Philippe

"Se questo aggiunge troppi costruttori, considera l'utilizzo di fabbriche invece di costruttori." Fondamentalmente differisci le eccezioni di runtime e potresti persino sbagliare le cose e finire per rimanere bloccato con un'implementazione del localizzatore di servizi.
MeTitus,

@Marco questa è la mia precedente risposta e hai ragione. Se ci sono molti costruttori, direi che la classe fa troppe cose :-) O considera una fabbrica astratta.
Philippe,

7

È in gran parte una questione di gusti personali. Personalmente tendo a preferire l'iniezione setter, perché credo che ti dia maggiore flessibilità nel modo in cui puoi sostituire le implementazioni in fase di esecuzione. Inoltre, i costruttori con molti argomenti non sono chiari secondo me e gli argomenti forniti in un costruttore dovrebbero essere limitati a argomenti non opzionali.

Finché l'interfaccia delle classi (API) è chiara su ciò di cui ha bisogno per svolgere il suo compito, sei bravo.


si prega di specificare perché si effettua il downvoting?
nkr1pt

3
Sì, i costruttori con molti argomenti sono cattivi. Ecco perché refactoring le classi con molti parametri del costruttore :-).
sleske,

10
@ nkr1pt: la maggior parte delle persone (me incluso) concorda sul fatto che l'iniezione del setter è errata se ti consente di creare una classe che fallisce in fase di esecuzione se l'iniezione non viene eseguita. Credo quindi che qualcuno abbia contestato la tua affermazione che si tratta di gusti personali.
sleske,

7

Personalmente preferisco il "modello" Estrai e Sostituisci piuttosto che iniettare dipendenze nel costruttore, in gran parte per il motivo delineato nella tua domanda. È possibile impostare le proprietà come virtuale quindi sovrascrivere l'implementazione in una classe testabile derivata.


1
Penso che il nome ufficiale del modello sia "Metodo modello".
davidjnelson,

6

Preferisco l'iniezione del costruttore perché aiuta a "far rispettare" i requisiti di dipendenza di una classe. Se è nel c'tor, un consumatore ha impostare gli oggetti per far compilare l'app. Se si utilizza l'iniezione setter, potrebbero non sapere di avere un problema fino al runtime e, a seconda dell'oggetto, potrebbe essere in ritardo nel runtime.

Di tanto in tanto uso ancora l'iniezione del setter quando l'oggetto iniettato potrebbe aver bisogno di un sacco di lavoro, come l'inizializzazione.


6

Perferisco l'iniezione del costruttore, perché questo sembra più logico. È come dire che la mia classe richiede queste dipendenze per fare il suo lavoro. Se si tratta di una dipendenza facoltativa, le proprietà sembrano ragionevoli.

Uso anche l'iniezione di proprietà per impostare cose su cui il contenitore non ha riferimenti come una vista ASP.NET su un presentatore creato usando il contenitore.

Non penso che rompa l'incapsulamento. I meccanismi interni dovrebbero rimanere interni e le dipendenze devono affrontare una preoccupazione diversa.


Grazie per la tua risposta. Sembra certamente che il costruttore sia la risposta popolare. Comunque penso che rompa l'incapsulamento in qualche modo. Iniezione pre-dipendenza, la classe dichiarerebbe e creerebbe un'istanza di tutti i tipi concreti necessari per svolgere il proprio lavoro. Con DI, le sottoclassi (e tutti gli istanti manuali) ora sanno quali strumenti sta usando una classe base. Aggiungete una nuova dipendenza e ora dovete concatenare l'istanza da tutte le sottoclassi, anche se non hanno bisogno di usare la dipendenza da soli.
Niall Connaughton,

Ho appena scritto una bella risposta lunga e l'ho persa a causa di un'eccezione su questo sito !! :( In sintesi, la baseclass viene solitamente utilizzata per riutilizzare la logica. Questa logica potrebbe facilmente entrare nella sottoclasse ... quindi potresti pensare alla baseclass e alla sottoclasse = una preoccupazione, che dipende da più oggetti esterni, che fai lavori diversi. Il fatto che tu abbia dipendenze, non significa che devi esporre tutto ciò che avresti precedentemente tenuto privato.
David Kiff

3

Un'opzione che potrebbe essere utile considerare è la composizione di dipendenze multiple complesse da semplici dipendenze singole. Cioè, definire le classi extra per le dipendenze composte. Questo rende le cose un po 'più facili per l'iniezione del costruttore WRT - meno parametri per chiamata - pur mantenendo la necessità di fornire tutte le dipendenze per istanziare.

Ovviamente ha molto senso se esiste un qualche tipo di raggruppamento logico di dipendenze, quindi il composto è più di un aggregato arbitrario, e ha più senso se ci sono più dipendenze per una singola dipendenza composta - ma il blocco di parametri "modello" ha è stato in giro per molto tempo, e la maggior parte di quelli che ho visto sono stati piuttosto arbitrari.

Personalmente, però, sono più un fan dell'uso di metodi / settatori di proprietà per specificare dipendenze, opzioni, ecc. I nomi delle chiamate aiutano a descrivere cosa sta succedendo. È una buona idea fornire esempi di frammenti di questo tipo e come assicurarsi che la classe dipendente esegua sufficienti controlli di errore. È possibile che si desideri utilizzare un modello a stati finiti per l'installazione.


3

Di recente mi sono imbattuto in una situazione in cui avevo più dipendenze in una classe, ma solo una delle dipendenze doveva necessariamente cambiare in ogni implementazione. Poiché le dipendenze di accesso ai dati e di registrazione degli errori verrebbero probabilmente modificate solo a scopo di test, ho aggiunto parametri opzionali per tali dipendenze e fornito implementazioni predefinite di tali dipendenze nel mio codice costruttore. In questo modo, la classe mantiene il suo comportamento predefinito a meno che non venga sovrascritta dal consumatore della classe.

L'uso di parametri opzionali può essere realizzato solo in framework che li supportano, come .NET 4 (sia per C # che per VB.NET, sebbene VB.NET li abbia sempre avuti). Naturalmente, puoi ottenere funzionalità simili semplicemente usando una proprietà che può essere riassegnata dal consumatore della tua classe, ma non ottieni il vantaggio dell'immutabilità fornito dall'assegnazione di un oggetto di interfaccia privata a un parametro del costruttore.

Detto questo, se stai introducendo una nuova dipendenza che deve essere fornita da ogni consumatore, dovrai riformattare il tuo costruttore e tutto il codice che consuma la tua classe. I miei suggerimenti di cui sopra si applicano davvero solo se hai il lusso di essere in grado di fornire un'implementazione predefinita per tutto il tuo codice attuale, ma se hai ancora la possibilità di sovrascrivere l'implementazione predefinita.


1

Questo è un vecchio post, ma se è necessario in futuro, forse è di qualche utilità:

https://github.com/omegamit6zeichen/prinject

Ho avuto un'idea simile e mi è venuta in mente questa struttura. Probabilmente è tutt'altro che completo, ma è un'idea di un quadro incentrato sull'iniezione di proprietà


0

Dipende da come si desidera implementare. Preferisco l'iniezione del costruttore ovunque ritenga che i valori che entrano nell'implementazione non cambiano spesso. Ad esempio: se la strategia compnay va con oracle server, configurerò i miei valori datsource per un bean che ottiene connessioni tramite l'iniezione del costruttore. Altrimenti, se la mia app è un prodotto e le probabilità che possa connettersi a qualsiasi db del cliente, implementerei tale configurazione db e l'implementazione multimarca attraverso l'iniezione setter. Ho appena preso un esempio ma ci sono modi migliori per implementare gli scenari che ho menzionato sopra.


1
Anche durante la codifica interna, codifico sempre dal punto di vista del fatto che sono un appaltatore indipendente che sviluppa codice destinato a essere ridistribuibile. Presumo che sarà open source. In questo modo, mi assicuro che il codice sia modulare e collegabile e segua i principi SOLIDI.
Fred

0

L'iniezione del costruttore rivela esplicitamente le dipendenze, rendendo il codice più leggibile e meno soggetto a errori di runtime non gestiti se gli argomenti sono controllati nel costruttore, ma si riduce davvero all'opinione personale, e più usi DI più più tendono a oscillare avanti e indietro in un modo o nell'altro a seconda del progetto. Personalmente ho problemi con odori di codice come costruttori con una lunga lista di argomenti, e sento che il consumatore di un oggetto dovrebbe conoscere le dipendenze per poter usare comunque l'oggetto, quindi questo è un caso per usare l'iniezione di proprietà. Non mi piace la natura implicita dell'iniezione di proprietà, ma la trovo più elegante, con un codice più pulito. D'altra parte, l'iniezione del costruttore offre un grado più elevato di incapsulamento,

Scegli l'iniezione per costruttore o per proprietà saggiamente in base al tuo scenario specifico. E non pensare di dover usare DI solo perché sembra necessario e impedirà cattivi odori di progettazione e codice. A volte non vale la pena usare uno schema se lo sforzo e la complessità superano i benefici. Keep it simple.

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.