contexto ambiental vs inyección de constructor

9

Tengo muchas clases principales que requieren ISessionContext de la base de datos, ILogManager para el registro e IService utilizado para comunicarse con otros servicios. Quiero usar la inyección de dependencia para esta clase utilizada por todas las clases principales.

Tengo dos posibles implementaciones. La clase principal que acepta IAmbientContext con las tres clases o inyecta para todas las clases las tres clases.

public interface ISessionContext 
{
    ...
}

public class MySessionContext: ISessionContext 
{
    ...
}

public interface ILogManager 
{

}

public class MyLogManager: ILogManager 
{
    ...
}

public interface IService 
{
    ...
}

public class MyService: IService
{
    ...
}

Primera solución

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)
{
    ...
}

segunda solución (sin contexto ambiental)

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

¿Cuál es la mejor solución en este caso?

usuario3401335
fuente
¿Qué se IServiceusa para comunicarse con otros servicios? Si IServicerepresenta una vaga dependencia de otros servicios, entonces suena como un localizador de servicios y no debería existir. Su clase debería depender de interfaces que describan explícitamente qué hará su consumidor con ellas. Ninguna clase necesita un servicio para proporcionar acceso a un servicio. Una clase necesita una dependencia que hace algo específico que necesita la clase.
Scott Hannen

Respuestas:

4

"Lo mejor" es demasiado subjetivo aquí. Como es común con tales decisiones, es una compensación entre dos formas igualmente válidas de lograr algo.

Si crea un AmbientContexte inyectar que en muchas clases, que está proporcionando potencialmente más información a cada uno de ellos de lo que necesitan (por ejemplo, la clase Foosólo se puede utilizar ISessionContext, pero que se está contando acerca ILogManagery ISessiontambién).

Si pasa cada uno a través de un parámetro, entonces le dice a cada clase solo las cosas que necesita saber. Pero, el número de parámetros puede crecer rápidamente y puede encontrar que tiene demasiados constructores y métodos con muchos parámetros muy repetidos, que podrían simplificarse a través de una clase de contexto.

Por lo tanto, se trata de equilibrar los dos y elegir el apropiado para sus circunstancias. Si solo tiene una clase y tres parámetros, personalmente no me molestaría AmbientContext. Para mí, el punto de inflexión probablemente sería cuatro parámetros. Pero eso es pura opinión. Su punto de inflexión probablemente será diferente al mío, así que elija lo que le parezca más adecuado.

David Arno
fuente
4

La terminología de la pregunta realmente no coincide con el código de ejemplo. El Ambient Contextes un patrón utilizado para tomar una dependencia de cualquier clase en cualquier módulo de la manera más fácil posible, sin contaminar cada clase para aceptar la interfaz de la dependencia, pero manteniendo la idea de inversión de control. Dichas dependencias generalmente se dedican al registro, la seguridad, la administración de sesiones, las transacciones, el almacenamiento en caché, la auditoría, por lo que cualquier preocupación transversal en esa aplicación. Es de alguna manera molesto para añadir una ILogging, ISecurity, ITimeProvidera los constructores y la mayoría de las veces no todas las clases necesitan, todo al mismo tiempo, por lo que entiendo su necesidad.

¿Qué pasa si la vida útil de la ISessioninstancia es diferente a la de la instancia ILogger? Tal vez la instancia de ISession debería crearse en cada solicitud y el ILogger una vez. Por lo tanto, tener todas estas dependencias gobernadas por un objeto que no es el contenedor en sí mismo no parece la opción correcta debido a todos estos problemas con la administración y localización de por vida y otros descritos en este hilo.

El IAmbientContexten la pregunta no resuelve el problema de no contaminar a todos los constructores. Todavía tiene que usarlo en la firma del constructor, claro, solo una vez esta vez.

Entonces, la forma más fácil NO es usar la inyección de constructor o cualquier otro mecanismo de inyección para tratar dependencias transversales, sino usar una llamada estática . En realidad, vemos este patrón con bastante frecuencia, implementado por el propio marco. Verifique Thread.CurrentPrincipal, que es una propiedad estática que devuelve una implementación de la IPrincipalinterfaz. También es configurable para que pueda cambiar la implementación si lo desea, por lo que no está acoplado a ella.

MyCore ahora se ve algo así como

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

        _repository.SaveProcessedData();

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

Este patrón y las posibles implementaciones han sido descritas en detalle por Mark Seemann en este artículo . Puede haber implementaciones que se basan en el contenedor de IoC que usted usa.

Desea evitar AmbientContext.Current.Logger, AmbientContext.Current.Sessionpor los mismos motivos descritos anteriormente.

Pero tiene otras opciones para resolver este problema: use decoradores, intercepción dinámica si su contenedor tiene esta capacidad o AOP. El contexto ambiental debería ser el último recurso debido al hecho de que sus clientes ocultan sus dependencias a través de él. Todavía usaría Contexto ambiental si la interfaz realmente imita mi impulso de usar una dependencia estática como DateTime.Nowo ConfigurationManager.AppSettingsy esta necesidad surge con bastante frecuencia. Pero al final, la inyección del constructor podría no ser una mala idea para obtener estas dependencias ubicuas.

Adrian Iftode
fuente
3

Yo evitaría el AmbientContext.

Primero, si la clase depende, AmbientContextentonces no sabes realmente lo que hace. Debe observar el uso de esa dependencia para determinar cuál de sus dependencias anidadas usa. Tampoco puede ver el número de dependencias y saber si su clase está haciendo demasiado porque una de esas dependencias podría representar varias dependencias anidadas.

En segundo lugar, si lo está utilizando para evitar dependencias de múltiples constructores, este enfoque alentará a otros desarrolladores (incluido usted) a agregar nuevos miembros a esa clase de contexto ambiental. Entonces el primer problema se agrava.

En tercer lugar, burlarse de la dependencia AmbientContextes más difícil porque tienes que decidir en cada caso si burlarte de todos sus miembros o solo de los que necesitas, y luego configurar un simulacro que devuelva esos simulacros (o dobles de prueba). su unidad prueba más difícil de escribir, leer y mantener.

Cuarto, carece de cohesión y viola el Principio de Responsabilidad Única. Es por eso que tiene un nombre como "AmbientContext", porque hace muchas cosas no relacionadas y no hay forma de nombrarlo de acuerdo con lo que hace.

Y probablemente viola el Principio de segregación de interfaz al introducir miembros de interfaz en clases que no los necesitan.

Scott Hannen
fuente
2

El segundo (sin el envoltorio de interfaz)

A menos que haya alguna interacción entre los diversos servicios que necesita encapsularse en una clase intermedia, solo complica su código y limita la flexibilidad cuando introduce la 'interfaz de interfaces'

Ewan
fuente