Search code examples
c#.netdependency-injectioninversion-of-controlninject

How to configure ninject to inject different dependency types into the same class?


I've run into what I believe must be a common dependency injection-related problem. I'm having trouble finding relevant examples, and I do not like the best solution I've been able to come up with.

public class WasherDryerFolderSystem : ILaundrySystem
{
    private IWasher _washer;
    private IDryer _dryer;
    private IFolder _folder;

    public WasherDryerFolderSystem(IWasher washer, IDryer dryer, IFolder folder)
    {...}

    public void DoLaundry()
    {
        _washer.Wash();
        _dryer.Dry();
        _folder.Fold();
    }
}


public class HandWasher : IWasher {...}
public class MachineWasher : IWasher {...}

public class HandDryer : IDryer {...}
public class MachineDryer : IDryer {...}

public class HandFolder : IFolder {...}
public class MachineFolder : IFolder {...}

Now in the main app I have something like

var laundrySystem = _kernel.Get<ILaundrySystem>(someUserInput);

What is a good way to configure the bindings required for something like this? Here's what I've been able to come up with thus far (that I don't like):

Bind<ILaundrySystem>().To<WasherDryerFolderSystem>()
    .Named(MACHINEWASH_HANDDRY_HANDFOLD)
    .WithConstructorArgument("washer", new MachineWasher())
    .WithConstructorArgument("dryer", new HandDryer())
    .WithConstructorArgument("folder", new HandFolder());

At first I didn't think this looked too bad, but when Washers and Dryers and Folders all have their own dependencies, this quickly gets ugly.

This feels to me like it should be a common problem, but I'm not finding anything that's much help. Do I have something designed incorrectly?


Solution

  • You could use a factory pattern:

    public interface ILaundrySystemFactory
    {
        ILaundrySystem  Create(string someUserInput);
    }
    
    public class LaundrySystemFactory : ILaundrySystemFactory
    {
        private readonly IKernel _kernel;
    
        public LaundrySystemFactory(IKernel kernel){
            _kernel = kernel;
        }
    
        public ILaundrySystem Create(string someUserInput)
        {
            if(someUserInput){
                var washer = _kernel.Get<MachineWasher>();
                var dryer = _kernel.Get<HandDryer>();
                var folder = _kernel.Get<HandFolder>();
            } else {            
                var washer = _kernel.Get<DifferentWasher>();
                var dryer = _kernel.Get<DifferentDryer>();
                var folder = _kernel.Get<DifferentFolder>();
            }
            return new WasherDryerFolderSystem(washer, dryer, folder);
        }
    }
    

    and then simply

    private readonly ILaundrySystemFactory  _laundrySystemFactory;
    
    ctor(ILaundrySystemFactory laundrySystemFactory){
        _laundrySystemFactory = laundrySystemFactory;
    }
    
    public UserInputMethod(string someUserInput)
    {
        var loundrySystem = laundrySystemFactory.Create(someUserInput);
        var loundry = loundrySystem.DoLaundry();
    }
    

    bindings:

    Bind<ILaundrySystemFactory>().To<LaundrySystemFactory>();
    

    (some DI containers might also need something like:)

    Bind<MachineWasher>().To<MachineWasher>();