17

I have a service class with the following ctor:

public class (IMessageService emailService, IMessageService smsService)
{ ... }

and two implementations of IMessageService (email and sms). How do I configure the container to resolve this constructor correctly? Is this where named instances come in, or is that for another scenario?

Gimby
  • 5,095
  • 2
  • 35
  • 47
Myles McDonnell
  • 12,943
  • 17
  • 66
  • 116

4 Answers4

38

You could use named instances or smart instances to solve this...

// Named instances
this.For<IMessageService>().Use<EmailService>().Named("emailService");
this.For<IMessageService>().Use<SmsService>().Named("smsService");



// Smart instances
var emailService = this.For<IMessageService>().Use<EmailService>();
var smsService = For<IMessageService>().Use<SmsService>();

this.For<ISomeService>().Use<SomeService>()
    .Ctor<IMessageService>("emailService").Is(emailService)
    .Ctor<IMessageService>("smsService").Is(smsService);

But I would argue that your design needs some work. The fact that your service knows the difference between an emailService an an SMS service is a violation of the Liskov Substitution Principle. A better approach than injecting 2 parameters of the same type is to use a composite pattern.

public class CompositeMessageService : IMessageService
{
    private readonly IMessageService[] messageServices;
    
    public CompositeMessageService(IMessageService[] messageServices)
    {
        this.messageServices = messageServices ?? throw new ArgumentNullException(nameof(messageServices));
    }
    
    public void Send(IMessage message)
    {
        foreach (var messageService in this.messageServices)
        {
            messageService.Send(message);
        }
    }
}

Your original service then needs to only accept a single instance of IMessageService. It does not need to know the details of what type of IMessageService it is dealing with.

public SomeService(IMessageService messageService)

In StructureMap, you can easily register all instances of IMessageService and it will automatically inject them into a constructor argument array of IMessageService.

this.Scan(scan =>
        {
            scan.TheCallingAssembly();
            scan.AssemblyContainingType<IMessageService>();
            scan.AddAllTypesOf<IMessageService>();
        });

Or you can inject instances explicitly.

        this.For<IMessageService>().Use<CompositeMessageService>()
            .EnumerableOf<IMessageService>().Contains(x =>
            {
                x.Type<EmailService>();
                x.Type<SmsService>();
            });

This means your configuration can be changed to change the order of which service is called first. With your current design, those details are hard coded into the service that accepts the 2 parameters.

Additionally, you gain the ability to add additional message services or remove existing message services without changing your design.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Fantastic answer, a real eye opener. Do you have any recommendations on how you can spot instances where the composite pattern can be used like this? – Joseph Woodward Feb 19 '15 at 14:16
  • 4
    Short answer - a service that has more than one dependency of the same type is a code smell where either a composite or decorator can be used. As for getting into the right mindset, personally I got there by reading [Dependency Injection in .NET](http://www.manning.com/seemann/) and some trial and err. There are a lot of problems that design patterns can solve that DI alone cannot, and that book is great for teaching when and how to apply those patterns. – NightOwl888 Feb 21 '15 at 23:06
  • Using your first example, how do you avoid the fact that the composite takes an array of all implementations... which includes the composite... which takes an array... and so on? – Ed S. Sep 17 '18 at 16:13
3

Turns out named instances are one possible solution:

 _.For<IMessageService >().Use<EmailService>().Named("emailService");
 _.For<IMessageService >().Use<EmailService>().Named("smsService");
Myles McDonnell
  • 12,943
  • 17
  • 66
  • 116
2
//First just write below lines in IOC    

this.For<IMessageService>().Use<EmailService>();
this.For<IMessageService >().Use<EmailService>();


//Then We can directly use it in our constructor injection of our class                             
//Where we need it
IEnumerable<IMessageService> messageServices;  

public ClassNeedsInjection(IEnumerable<IMessageService> messageServices)                         
{
    this.messageServices=messageServices;
    foreach (var messageService in this.messageServices)
    {
        //use both the objects as you wish
    }
}
1

Don't need to be complicated. Just simply register all the implementations of an interface.

 this.For<IMessageService >().Use<EmailService>(); 
 this.For<IMessageService >().Use<SmsService>();

Then structuremap will automatically inject all implementation of the IMessageService interface into any constructor that has IEnumerable/list/array of an interface. Like below:

private IEnumerator<IMessageService> _current; 
public SomeClass(IEnumerable<IMessageService> features) {
    _current = features.GetEnumerator();
    } 

Hope it helps

Hung Vu
  • 5,624
  • 2
  • 30
  • 27