DependencyInjection - Constructor over-injection smell vs service locator - dove è l'approccio corretto?

5

Sto cercando di migliorare i miei progetti MVC e ho letto alcuni articoli su DI, contenitori IoC, Iniezione del costruttore e locatori di servizi. Volevo andare con Ninject per aiutare con le dipendenze, ma sono rimasto perplesso con alcune delle cose che ho letto.

Il più controverso sarebbe quello ampiamente discusso qui link (entrambe le parti).

E poi alcune risposte ad esso, come la risposta di Mark Seemann , e un follow up

Jeffrey espone le mie paure e le affronta in modo scorretto - tuttavia, non sono sicuro di cosa fare nel mio caso.

Ad esempio, vediamo un controller MVC. Funge da livello di comunicazione tra un'app MCV e una WebAPI. Sono un po 'concentrato sul numero di dipendenze che avrei dovuto fare con Constructor Injection.

public class OcrController : ConnectorControllerBase, IOcrController
{
    private readonly ILogger logger;
    private readonly IConnectorConfigurator config;
    private readonly HttpClient client;
    private readonly IOcrConnector connector;

    public OcrController(
            ILogger logger, 
            IConnectorConfigurator config, 
            HttpClient client, 
            IOcrConnector connector) 
    {
        this.logger = logger;
        this.config = config;
        this.client = client;
        this.connector = connector;
    }
}

Mentre IOcrConnector e HttpClient sono chiare dipendenze da cui non ho problemi, sono preoccupato per gli altri due. A seconda di un'azione, richiede un IConfiguration per leggere qualcosa dalla config e in alcuni casi potrebbe essere necessario un Logger.

Lo stesso sarebbe vero per molte delle mie classi - una o due dipendenze reali, e il logger e la configurazione e forse qualcos'altro in futuro.

Al momento sto utilizzando un ServiceLocator semplice e scarso, in cui invece di passare a ILOT e IConfig, li creo quando necessario:

protected ILogger Logger => this.logger 
    ?? (this.logger = ComponentFactory.GetLogger(this.GetType()));

protected IConnectorConfigurator Config => this.config 
    ?? (this.config = ComponentFactory.GetConfig());

Il 'Factory' è molto semplice, perché restituisce semplicemente il mio wrapper di ILogger come di seguito:

public static ILogger GetLogger(Type type)
{
    return new Log4NetLogger(type);
}

Questa fabbrica vive in un assemblaggio separato, quindi mi sembra che disaccordi sufficientemente il concreto ILogger dai progetti attuali ...?

Non ho intenzione di scambiare ILogger o IConfig (che ora è solo un wrapper per ConfigurationManager integrato) in fase di esecuzione o qualsiasi altra cosa, ma quando / se si presenta la necessità, cambierei semplicemente il tipo restituito da GetLogger (Tipo di tipo); metodo.

Quindi, fatemi sapere se questo tipo di mix di DI e ServiceLocator appropriati ha senso? Un'altra alternativa che penso potrebbe essere passare un IComponentFactory nel costruttore (invece di avere una statica globalmente accessibile?) Ma questo è ancora un localizzatore di servizi, posto solo altrove ...

Per favore fatemi sapere cosa ne pensate

    
posta Bartosz 07.05.2017 - 23:53
fonte

3 risposte

4

Nel tuo esempio collegato il blogger sta sostenendo che il codice dovrebbe essere refactored perché la dipendenza "è passata giù per la catena" piuttosto che essere usata direttamente.

È un odore del codice 'Hobo parameter' piuttosto che qualsiasi cosa abbia a che fare con DI. Potrebbe ripararlo iniettando in Order invece di OrderProcessor

Nel tuo esempio il tuo controller utilizzerà presumibilmente direttamente ILogger? Quindi non è lo stesso problema.

I controller MVC Moveover richiederanno sempre l'iniezione di molte classi di servizio poiché devono essere necessariamente progettate in modo procedurale.

Considera che una chiamata al metodo sul controller che accetta un parametro Order deve costruire quell'oggetto dai dati di valore puro nella richiesta tramite associazione.

Poiché il parametro di input Ordine viene creato da questa pipeline, è necessario iniettare la dipendenza nel controller o nel raccoglitore.

In entrambi i casi la pipeline deve agire come impostazione della dipendenza per la chiamata esattamente nel modo in cui si blocca il blogger, perché l'oggetto Ordine deve essere creato dai dati in entrata anziché essere passato per riferimento

    
risposta data 08.05.2017 - 04:28
fonte
1

C'è anche una differenza tra l'iniezione di una nuova istanza ogni volta, o l'iniezione della stessa istanza di nuovo: quest'ultima è molto più veloce.

Detto questo, nel caso del logger, è possibile creare un'istanza e iniettarla dove è richiesta in un batter d'occhio. Lo stesso potrebbe valere per il tuo motore di configurazione.

    
risposta data 08.05.2017 - 12:36
fonte
1

Il passaggio in ILogger mi sembra un po 'eccessivo visto che l'utilizzo di Log4Net o persino di uno dei numerosi wrapper del logger ti consente di ottenere facilmente il tuo logger attraverso il log factory standard:

private static ILog logger = 
    LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

Non vedo come utilizzare l'injection dependency risolva davvero qualsiasi cosa per la registrazione. Anche il wrapper di registrazione di Microsoft in .Net Standard consente questo tipo di accesso al logger senza sacrificare la possibilità di sostituire l'implementazione con la configurazione corretta.

Che ne lascia solo uno: IConnectorConfigurator . Personalmente, probabilmente vivrò con l'iniezione del costruttore fino a quando dimostreremo che si tratta di un problema reale. Sembra il tipo di cose che possono essere un singleton nella tua app, nel qual caso la stessa istanza verrebbe fornita a tutte le implementazioni.

Devi chiedertelo:

  • Sto affrontando un problema misurabile? (cioè non in grado di soddisfare i requisiti di prestazione)
  • La complessità aggiunta del problema vale la complessità della soluzione proposta?
  • In che modo influirà sul tempo di risposta per correggere i bug nella mia app?

Se hai una dozzina o più parametri di costruzione, probabilmente dovresti ripensare alla divisione delle responsabilità nei tuoi componenti. In questo caso, ho la sensazione che la soluzione proposta aggiunga maggiore complessità e sorpresa ai manutentori di quanto non risolva. Non fraintendermi, c'è un tempo e un luogo per deviare dalla semplicità dell'iniezione del costruttore - Penso che in questo caso è probabilmente eccessivo.

    
risposta data 08.05.2017 - 18:37
fonte