contesto ambientale rispetto all'iniezione del costruttore

7

Ho molte core classi che richiedono ISessionContext del database, ILogManager per log e IService utilizzato per comunicare con altri servizi. Voglio utilizzare l'iniezione di dipendenza per questa classe utilizzata da tutte le classi principali.

Ho due possibili implementazioni. La classe principale che accetta IAmbientContext con tutte e tre le classi o inietta per tutte le classi le tre classi.

public interface ISessionContext 
{
    ...
}

public class MySessionContext: ISessionContext 
{
    ...
}

public interface ILogManager 
{

}

public class MyLogManager: ILogManager 
{
    ...
}

public interface IService 
{
    ...
}

public class MyService: IService
{
    ...
}

Prima soluzione:

public class AmbientContext
{
    private ISessionContext sessionContext;
    private ILogManager logManager;
    private IService service;

    public AmbientContext(ISessionContext sessionContext, ILogManager logManager, IService service)
    {
        this.sessionContext = sessionContext;
        this.logManager = logManager;
        this.service = service;
    }
}


public class MyCoreClass(AmbientContext ambientContext)
{
    ...
}

seconda soluzione (senza ambientcontext)

public MyCoreClass(ISessionContext sessionContext, ILogManager logManager, IService service)
{
    ...
}

Quale è la soluzione migliore in questo caso?

    
posta user3401335 09.01.2018 - 10:45
fonte

4 risposte

4

"Il migliore" è troppo soggettivo qui. Come è comune con tali decisioni, è un compromesso tra due modi ugualmente validi per ottenere qualcosa.

Se crei un AmbientContext e lo immetti in molte classi, puoi potenzialmente fornire più informazioni di quante ne abbiano bisogno (ad esempio, la classe Foo può utilizzare solo ISessionContext , ma viene detto su ILogManager e ISession troppo).

Se passi ognuno attraverso un parametro, allora comunichi a ogni classe solo le cose di cui ha bisogno di sapere. Ma il numero di parametri può crescere rapidamente e potresti scoprire di avere troppi costruttori e metodi con molti parametri altamente ripetuti, che potrebbero essere semplificati tramite una classe di contesto.

Quindi è un caso di bilanciare i due e scegliere quello appropriato per le circostanze. Se hai solo una classe e tre parametri, personalmente non mi preoccuperei di AmbientContext . Per me, il punto di svolta sarebbe probabilmente di quattro parametri. Ma questa è pura opinione. Il tuo punto di svolta sarà probabilmente diverso dal mio, quindi vai con quello che ti sembra giusto.

    
risposta data 09.01.2018 - 10:57
fonte
2

Eviterei AmbientContext .

Innanzitutto, se la classe dipende da AmbientContext , in realtà non sai cosa fa. Bisogna considerare il suo uso di tale dipendenza per capire quale delle sue dipendenze annidate utilizza. Inoltre, non è possibile esaminare il numero di dipendenze e indicare se la classe sta facendo troppo perché una di queste dipendenze potrebbe effettivamente rappresentare diverse dipendenze nidificate.

Secondo, se lo stai usando per evitare dipendenze da più costruttori, questo approccio incoraggerà altri sviluppatori (incluso te stesso) ad aggiungere nuovi membri a quella classe di contesto ambientale. Quindi il primo problema è aggravato.

Terzo, deridere la dipendenza su AmbientContext è più difficile perché devi capire in ogni caso se prendere in giro tutti i suoi membri o solo quelli di cui hai bisogno, e quindi impostare una simulazione che restituisce quei mock (o prova raddoppia). Ciò rende la tua unità più difficile da scrivere, leggere e mantenere.

In quarto luogo, manca di coesione e viola il principio di responsabilità unica. Ecco perché ha un nome come "AmbientContext", perché fa un sacco di cose non correlate e non c'è modo di nominarlo in base a ciò che fa.

E probabilmente viola il principio di segregazione dell'interfaccia introducendo i membri dell'interfaccia in classi che non ne hanno bisogno.

    
risposta data 11.01.2018 - 13:36
fonte
2

La terminologia della domanda non corrisponde esattamente al codice di esempio. Il Ambient Context è un pattern utilizzato per afferrare una dipendenza da qualsiasi classe in qualsiasi modulo nel modo più semplice possibile, senza inquinare ogni classe per accettare l'interfaccia della dipendenza, mantenendo comunque l'idea di inversione del controllo. Tali dipendenze sono solitamente dedicate alla registrazione, alla sicurezza, alla gestione delle sessioni, alle transazioni, alla memorizzazione nella cache, alla verifica in modo da soddisfare ogni esigenza trasversale in quella applicazione. È in qualche modo fastidioso aggiungere un ILogging , ISecurity , ITimeProvider ai costruttori e il più delle volte non tutte le classi hanno bisogno di tutte allo stesso tempo, quindi capisco le tue necessità.

Che cosa succede se la durata dell'istanza ISession è diversa da ILogger uno? Forse l'istanza di ISession dovrebbe essere creata una volta su ogni richiesta e su ILogger. Quindi avere tutte queste dipendenze governate da un oggetto che non è il contenitore stesso non sembra la scelta giusta a causa di tutti questi problemi con gestione e localizzazione a vita e altri descritti in questa discussione.

Il IAmbientContext nella domanda non risolve il problema di non inquinare ogni costruttore. Devi ancora usarlo nella firma del costruttore, certo, una volta solo questa volta.

Quindi il modo più semplice NON è usare l'iniezione del costruttore o qualsiasi altro meccanismo di iniezione per gestire le dipendenze trasversali, ma usando una chiamata statica . In realtà, vediamo questo schema abbastanza spesso, implementato dal framework stesso. Seleziona Thread.CurrentPrincipal che è una proprietà statica che restituisce un'implementazione dell'interfaccia IPrincipal . È anche impostabile in modo da poter cambiare l'implementazione, se ti piace così, quindi non sei abbinato ad esso.

MyCore sembra ora qualcosa di simile

public class MyCoreClass
{
    public void BusinessFeature(string data)
    {
        LoggerContext.Current.Log(data);

        _repository.SaveProcessedData();

        SessionContext.Current.SetData(data);
        ...etc
    }
}

Questo modello e le possibili implementazioni sono state descritte in dettaglio da Mark Seemann in questo articolo . Potrebbero esserci implementazioni che si basano sul contenitore IoC che utilizzi.

Vuoi evitare AmbientContext.Current.Logger , AmbientContext.Current.Session per gli stessi motivi descritti sopra.

Ma hai altre opzioni per risolvere questo problema: usa decoratori, intercettazione dinamica se il tuo contenitore ha questa capacità o AOP. L'Ambient Context dovrebbe essere l'ultima risorsa perché i suoi clienti nascondono le loro dipendenze attraverso di essa. Userei ancora Ambient Context se l'interfaccia in realtà imita il mio impulso di usare una dipendenza statica come DateTime.Now o ConfigurationManager.AppSettings e questa necessità aumenta abbastanza spesso. Ma alla fine l'iniezione del costruttore potrebbe non essere una cattiva idea ottenere queste dipendenze onnipresenti.

    
risposta data 12.01.2018 - 23:43
fonte
1

Il secondo (senza il wrapper dell'interfaccia)

A meno che non ci sia un'interazione tra i vari servizi che devono essere incapsulati in una classe intermedia, ciò complica il tuo codice e limita la flessibilità quando introduci l'interfaccia delle interfacce

    
risposta data 09.01.2018 - 10:59
fonte

Leggi altre domande sui tag