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?
c#
dependency-injection
usuario3401335
fuente
fuente
IService
usa para comunicarse con otros servicios? SiIService
representa 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.Respuestas:
"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
AmbientContext
e 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 claseFoo
sólo se puede utilizarISessionContext
, pero que se está contando acercaILogManager
yISession
tambié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.fuente
La terminología de la pregunta realmente no coincide con el código de ejemplo. El
Ambient Context
es 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 unaILogging
,ISecurity
,ITimeProvider
a 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
ISession
instancia es diferente a la de la instanciaILogger
? 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
IAmbientContext
en 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
IPrincipal
interfaz. 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í comoEste 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.Session
por 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.Now
oConfigurationManager.AppSettings
y 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.fuente
Yo evitaría el
AmbientContext
.Primero, si la clase depende,
AmbientContext
entonces 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
AmbientContext
es 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.
fuente
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'
fuente