contesto ambientale vs iniezione del costruttore


9

Ho molte classi principali che richiedono ISessionContext del database, ILogManager per log e IService utilizzati per comunicare con un altro servizio. Voglio usare l'iniezione di dipendenza per questa classe usata 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)
{
    ...
}

Qual è la soluzione migliore in questo caso?


Che cos'è " IServiceutilizzato per comunicare con un altro servizio?" Se IServicerappresenta una vaga dipendenza da altri servizi, allora sembra un localizzatore di servizi e non dovrebbe esistere. La tua classe dovrebbe dipendere da interfacce che descrivono esplicitamente cosa farà il loro consumatore con loro. Nessuna classe ha mai bisogno di un servizio per fornire l'accesso a un servizio. Una classe ha bisogno di una dipendenza che fa qualcosa di specifico di cui la classe ha bisogno.
Scott Hannen,

Risposte:


4

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

Se ne crei una AmbientContexte la inietti in molte classi, stai potenzialmente fornendo più informazioni a ognuna di esse di quelle di cui hanno bisogno (ad esempio, la classe Foopuò usare solo ISessionContext, ma viene detta ILogManagere ISessionanche).

Se passi ciascuno attraverso un parametro, allora dici a ogni classe solo di quelle cose che deve sapere. Tuttavia, il numero di parametri può aumentare rapidamente e potresti scoprire che hai troppi costruttori e metodi con molti parametri altamente ripetuti, che potrebbero essere semplificati tramite una classe di contesto.

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


4

La terminologia della domanda non corrisponde realmente al codice di esempio. Questo Ambient Contextè un modello usato per afferrare una dipendenza da qualsiasi classe in qualsiasi modulo il più semplice possibile, senza inquinare ogni classe per accettare l'interfaccia della dipendenza, ma mantenendo comunque l'idea di inversione del controllo. Tali dipendenze sono di solito dedicate alla registrazione, alla sicurezza, alla gestione delle sessioni, alle transazioni, alla memorizzazione nella cache, all'audit, quindi a qualsiasi preoccupazione trasversale in tale applicazione. E 'in qualche modo fastidioso per aggiungere un ILogging, ISecurity, ITimeProviderai costruttori e il più delle volte non tutte le classi bisogno di tutto, allo stesso tempo, quindi capisco la vostra esigenza.

Cosa succede se la durata ISessiondell'istanza è diversa da ILoggerquella? Forse l'istanza ISession dovrebbe essere creata su ogni richiesta e su ILogger una volta. 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 la gestione e la localizzazione a vita e altri descritti in questo thread.

La IAmbientContextdomanda non risolve il problema di non inquinare tutti i costruttori. 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. Controllare Thread.CurrentPrincipal che è una proprietà statica che restituisce un'implementazione IPrincipaldell'interfaccia. È anche configurabile in modo da poter modificare l'implementazione se lo desideri, quindi non sei accoppiato ad esso.

MyCore sembra ora qualcosa del genere

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 stesso che usi.

Vuoi evitare AmbientContext.Current.Logger, AmbientContext.Current.Sessionper 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. Il contesto ambientale dovrebbe essere l'ultima risorsa a causa del fatto che i suoi clienti nascondono le loro dipendenze attraverso di esso. Userei ancora Ambient Context se l'interfaccia imita davvero il mio impulso di usare una dipendenza statica come DateTime.Nowo ConfigurationManager.AppSettingse questa necessità aumenta abbastanza spesso. Ma alla fine l'iniezione del costruttore potrebbe non essere una cattiva idea per ottenere queste dipendenze onnipresenti.


3

Eviterei il AmbientContext.

In primo luogo, se la classe dipende da AmbientContextallora, in realtà non sai cosa fa. Devi guardare al suo uso di quella dipendenza per capire quale delle sue dipendenze nidificate usa. Inoltre, non puoi esaminare il numero di dipendenze e dire se la tua classe sta facendo troppo perché una di queste dipendenze potrebbe effettivamente rappresentare diverse dipendenze nidificate.

In secondo luogo, se lo si utilizza per evitare dipendenze multiple del costruttore, questo approccio incoraggerà altri sviluppatori (incluso te stesso) ad aggiungere nuovi membri a quella classe di contesto ambientale. Quindi il primo problema è aggravato.

In terzo luogo, deridere la dipendenza AmbientContextè più difficile perché devi capire in ogni caso se deridere tutti i suoi membri o solo quelli di cui hai bisogno, e quindi impostare un finto che restituisca quelle beffe (o prova i doppi). la tua unità è più difficile da scrivere, leggere e mantenere.

In quarto luogo, manca di coesione e viola il principio della responsabilità singola. Ecco perché ha un nome come "AmbientContext", perché fa molte 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.


2

Il secondo (senza il wrapper di interfaccia)

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

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.