Il passaggio del contenitore DI in fabbrica è sempre un antipattern?

2

In genere risponderei sempre a "sì" a questa domanda, perché a questo punto il tuo contenitore DI è diventato un localizzatore di servizi e stai perdendo i vantaggi di DI.

Tuttavia, mi chiedo se sia accettabile nel seguente scenario specifico.

  • Ho un'applicazione con un contenitore DI.
  • Il contenitore DI è responsabile della costruzione di istanze di componenti.
  • Al momento della costruzione del componente, il contenitore risolve gli argomenti del costruttore del componente (Reflection) e inietta tutte le dipendenze applicabili - dalle sue istanze memorizzate nella cache o costruendole di nuovo
  • Istanza costruita restituita al chiamante e memorizzata nella cache nel contenitore - la prossima volta che viene richiesta un'istanza di questo componente, verrà restituito quello attualmente esistente.

Quindi il contenitore ha due ruoli: applicazione del modello singleton e gestione delle dipendenze.

Ma questo non è veramente rilevante per la domanda; Sono a mio agio con l'operazione container, fa bene il suo lavoro e mi sento a mio agio nell'utilizzarlo. Interagisce solo direttamente durante il bootstrap dell'applicazione per creare istanze di componenti principali con le loro dipendenze e ritengo che questo sia il modo corretto di fare le cose.

Quindi non vedrai $this -> DIContainer in nessun'altra parte nell'applicazione tranne i componenti orchestrator di livello superiore.

Scusa il preambolo, ma voglio chiarire le cose. C'è una eccezione alla frase sopra.

L'applicazione supporta i plugin e, naturalmente, non possiamo anticipare le dipendenze dei plugin in anticipo. Non abbiamo idea di dove sia arrivato questo plugin o di quali componenti ha bisogno per fare il suo lavoro.

Inizialmente questo non è un problema: il contenitore DI è stato creato per fare questo lavoro! Chiamiamo solo $this -> DIContainer -> instance("MyPlugin") e il problema è risolto.

Ma è più difficile. Si scopre che i plugin possono essere compilati solo da una speciale fabbrica di plugin, che si occupa di molte operazioni di stalla per ottenere l'istanza del plugin, caricando la sua definizione dal filesystem, verificando che sia valida per la nostra versione API, assicurando che sia abilitato , ottenere dati di configurazione statici da inserire nel plug-in ecc.

Quindi la fabbrica dovrebbe essere il punto di accesso per ogni consumatore che desidera ottenere un plug-in istanziato.

L'unico problema è che se la fabbrica deve creare un'istanza del plug-in, ha bisogno dell'accesso diretto al contenitore DI per chiamare $this -> DIContainer -> ("MyPlugin") .

Il che significa che la costruzione di un'istanza PluginFactory richiede il passaggio a un'istanza DIContainer , che a un certo livello sembra errata .

Ora, non sono del tutto contrario a questo approccio. Ecco cosa vedo come vantaggi / svantaggi.

Buono

  • È ovvio dove andare per ottenere un'istanza di plugin, PluginFactory , e non è necessario andare in due posti
  • La fabbrica di plug-in potrebbe essere considerata un componente di livello superiore
  • Non è interamente contro DI, ma potrei spingerlo un po '... non possiamo probabilmente anticipare le dipendenze dei plugin, quindi l'unico modo in cui la fabbrica di plugin può fare il suo lavoro è se può accedere a tutte le possibili dipendenze - a sua volta, il contenitore DI in sé è l'unico componente in grado di soddisfare tale requisito.

Bad

  • Il contenitore DI è stato distribuito e non è più protetto nel componente root dell'applicazione.
  • Si potrebbe dire che la fabbrica dei plugin sta facendo troppo; forse i consumatori dovrebbero chiamare la fabbrica per caricare il plug-in dal filesystem / fare il file boilerplate, quindi rimandare al contenitore DI per ottenere successivamente un'istanza. Come richiedere alla fabbrica di fare qualcosa, e andare al negozio per ottenerlo.
  • In realtà portare il contenitore DI nella fabbrica di plugin come una dipendenza è di per sé potenzialmente problematico - se assumiamo che la fabbrica di plugin sia costruita attraverso DI stesso (ha altre dipendenze oltre al contenitore), probabilmente il contenitore DI sarà probabilmente iniettato in sé stesso, quindi può essere risolto quando si tratta di analizzare l'argomento DIContainer della factory, il che mi sembra estremamente fragile.

Come spesso, scrivere tutto questo mi ha già aiutato lungo la strada, e sto esaminando ulteriormente il mio punto sopra, forse la fabbrica di plugin sta facendo troppo. Forse la fabbrica ha solo bisogno di ottenere i pezzi in posizione, quindi i componenti di livello superiore possono ottenere istanze di plugin da DI.

Tuttavia sono interessato a sentire i pensieri. Sembra un momento in cui potrebbe essere giusto rendere disponibile il contenitore DI per un componente non di primo livello, ma ci sono chiaramente dei dubbi.

    
posta Ilmiont 12.11.2018 - 16:52
fonte

2 risposte

1

Quindi, è sicuramente un anti-pattern per passare il contenitore DI in giro. Ciò non significa che non c'è mai un caso in cui lo fai per convenienza. È solo un avvertimento per dire: "Se stai facendo questo non stai seguendo il pattern DI"

Ora, nel caso di un plug-in di terze parti, non credo che mi piacerebbe registrarmi o risolvere il plugin tramite DI.

Voglio che il plugin funzioni nella mia sandbox. Se gli do accesso alla DI, potrebbe fare qualcosa del tipo:

public class MyBadPlugin 
{
    public MyBadPlugin(IContainer c)
    {
        c.DeRegister<CoreComponent>();
        c.Register<EvilCoreComponent>();
    }
}

O almeno rompere la mia sandbox.

Inoltre, i buoni sviluppatori di plug-in si aspettano una specie di ambito di plugin documentato.

Sicuramente ottimo se do loro l'accesso in modalità dio, ma devo anche documentare tutto il mio codice interno e preoccuparmi di cosa succede quando è chiamato inaspettatamente dal plugin X

Sembra una ricetta per il disastro con plug-in che probabilmente si infrangono l'un l'altro e la mia app.

Quindi in sintesi:

Certo, passaci sopra, SE stai consentendo solo plug-in sviluppati internamente in cui sono completamente affidabili e gli sviluppatori conoscono il codice base. Comprendi il pattern DI abbastanza bene da permetterti di infrangere la regola.

Non passarlo in SE stai consentendo plug-in di terze parti. È un buco di sicurezza e un problema di stabilità.

    
risposta data 12.11.2018 - 17:12
fonte
0

Secondo me è "ok" iniettare il contenitore stesso in una fabbrica. Se vuoi giustificarlo nella tua mente, non chiamarla fabbrica: chiamala "estensione contenitore". Fa tutto parte del sottosistema responsabile dell'istanziazione e dell'ambito della vita: non esiste una regola che dica che un contenitore DI deve essere esattamente una classe o un oggetto.

Detto questo, se si desidera bloccare il contenitore lontano dalla fabbrica, è possibile iniettare un metodo di fabbrica, invece, passando il contenitore in una chiusura. Il metodo accetta un nome di plugin e restituisce un'istanza.

//Composition root
container.RegisterInstance<Func<string,IPlugin>>
(  
    pluginName => container.instance(pluginName) 
);

//Your factory
class PluginFactory
{
    protected readonly Func<string,IPlugin> _getter;

    public PluginFactory(Func<string,IPlugin> getter)
    {
        _getter = getter;
    }

    public IPlugin GetPlugin(string name)
    {
        return _getter(name);
    }
}

Ciò offre una separazione dei problemi leggermente migliore perché mantiene la logica del contenitore prevalentemente privata e espone solo esattamente ciò di cui la fabbrica ha bisogno. Sarà inoltre più facile per un test di unità sostituire un modello o uno stub per il metodo factory quando si crea un'istanza di fabbrica.

    
risposta data 12.12.2018 - 22:13
fonte

Leggi altre domande sui tag