Selección de tipo de dependency injection

Recientemente me encontré con un problema en el que tengo que seleccionar un tipo basado en un parámetro. Por ejemplo: una clase utilizada para enviar notificaciones que debe seleccionar el canal correcto (correo electrónico, sms, …) basado en un parámetro de entrada.

Me veo algo como esto:

public class NotificationManager { IEmail _email; ISms _sms; public NotificationManager (IEmail email, ISMS sms) { _email = email; _sms = sms; } public void Send(string type) { switch(type) { case "email": _email.send; break; case "sms": _sms.send; break; } } } 

El problema aquí es que cuando uso este tipo de construcción, el constructor crece rápidamente muy grande con todos los diferentes métodos de envío de notificaciones.

Realmente no me gusta esto, y hace que las pruebas unitarias de esta unidad de selección sean inmanejables.

No puedo simplemente decir un new email(); porque el correo electrónico de tipo notificación se basará en IEmailManager, y esto solo moverá el problema.

¿Hay algún tipo de patrón que haga lo mismo, pero de una manera mejor y más limpia?

Le sugiero que combine sus interfaces IEmail e ISms en un servicio IMessageService (siempre que no infrinja el principal de sustitución de Liskov ) y que use un patrón de estrategia para permitir que su servicio de notificación pueda seleccionar el tipo (o tipos) de IMessageService que se usan

Refactorizar a IMessageService

 public interface IMessageService { void Send(string subject, string body); bool AppliesTo(IEnumerable providers); } public class EmailMessageService : IMessageService { public EmailMessageService(/* inject dependencies (and configuration) here */) { // Set dependencies to private (class level) variables } public void Send(string subject, string body) { // Implementation - use dependencies as appropriate } public bool AppliesTo(IEnumerable providers) { return providers.Contains("email"); } } public class SmsMessageService : IMessageService { public SmsMessageService(/* inject dependencies (and configuration) here */) { // Set dependencies to private (class level) variables } public void Send(string subject, string body) { // Implementation - use dependencies as appropriate } public bool AppliesTo(IEnumerable providers) { return providers.Contains("sms"); } } 

Implementar el patrón de estrategia

 public interface IMessageStrategy { void Send(string message, string body, string provider); void Send(string message, string body, IEnumerable providers); } public class MessageStrategy : IMessageStrategy { private readonly IMessageService[] messageServices; public MessageStrategy(IMessageService[] messageServices) { if (messageServices == null) throw new ArgumentNullException("messageServices"); this.messageServices = messageServices; } public void Send(string message, string body, string provider) { string[] providers = provider.Split(';').Select(p => p.ToLower().Trim()).ToArray(); this.Send(message, body, providers); } public void Send(string message, string body, IEnumerable providers) { foreach (IMessageService messageService in messageServices) { if (messageService.AppliesTo(providers)) { messageService.Send(message, body); } } } } 

Uso

En su contenedor DI, registre todos los tipos que coincidan con IMessageService para que se resuelvan como una matriz. Por ejemplo, en StructureMap:

 container.For().Use(); container.For().Use(); 

O, alternativamente, puede usar Escanear para recoger automáticamente nuevos tipos que se agregan después del hecho.

 var container = new Container(x => x.Scan(scan => { scan.TheCallingAssembly(); scan.WithDefaultConventions(); scan.AddAllTypesOf(); })); 

De cualquier forma, registrar los tipos con el contenedor es todo lo que necesita para satisfacer la dependencia de IMessageService[] .

Luego, solo se trata de inyectar IMessageStrategy en una clase que requiere mensajería y pasar la cadena mágica para seleccionar qué tipos de servicios de mensajes usar.

 public class SomeService : ISomeService { private readonly IMessageStrategy messageStrategy; public SomeService(IMessageStrategy messageStrategy) { if (messageStrategy == null) throw new ArgumentNullException("messageStrategy"); this.messageStrategy = messageStrategy; } public void DoSomething() { // Send a message via email this.messageStrategy.Send("This is a test", "Hello", "email"); // Send a message via SMS this.messageStrategy.Send("This is a test", "Hello", "sms"); // Send a message via email and SMS this.messageStrategy.Send("This is a test", "Hello", "email;sms"); } } 

Tenga en cuenta que si toma este enfoque, su clase de EmailStrategy no tendrá que cambiar si más tarde decide agregar o eliminar un servicio de IMessageService ; solo necesita cambiar la configuración de DI.