Search code examples
c#dependency-injectioncastle-windsor

How to use Castle Windsor typed factory in this case?


Simple DI example:

public interface INumberToWordConverter
{
    string ConvertNumber(int number);
}

public interface IOutputManager
{
    void Write<T>(string who, T what);
}

public interface INumberProvider
{
    int GenerateNumber();
}

public class PlayWithDI
{
    private IOutputManager _outputManagerService;
    private INumberProvider _numberProviderService;
    private INumberToWordConverter _numberToWordConverterService;

    private PlayWithDI() { }
    public PlayWithDI(
        IOutputManager outputManagerService,
        INumberProvider numberProviderService,
        INumberToWordConverter numberToWordConverterService)
    {
        if (outputManagerService == null)
            throw new ArgumentNullException(nameof(outputManagerService));
        if (numberProviderService == null)
            throw new ArgumentNullException(nameof(numberProviderService));
        if (numberToWordConverterService == null)
            throw new ArgumentNullException(nameof(numberToWordConverterService));

        _outputManagerService = outputManagerService;
        _numberProviderService = numberProviderService;
        _numberToWordConverterService = numberToWordConverterService;
    }

    public void Execute()
    {
        int number = _numberProviderService.GenerateNumber();
        string wordOfNumber = _numberToWordConverterService.ConvertNumber(number);
        _outputManagerService.Write(nameof(PlayWithDI), wordOfNumber);
    }

Example implementations (just ctors):

// Implements INumberProvider
public RandomNumberProvider(
        int min, int max,
        IOutputManager outputManagerService)
{
    ...
}

// Implements INumberToWordConverter
public ItalianNumberToWordConverter(
      IOutputManager outputManagerService)
{
    ...
}

// Implements IConsoleManager
public ConsoleOutputManager()
{
    ...
}

If I know which min and max give to RandomNumberProvider, I would just resolve like this:

    public void Install(
        IWindsorContainer container, 
        IConfigurationStore store)
    {
        container.Register(
            Component.For<PlayWithDI>());

        container.Register(
            Component.For<IOutputManager>()
            .ImplementedBy<ConsoleOutputManager>());

        container.Register(
            Component.For<INumberProvider>()
            .ImplementedBy<RandomNumberProvider>()
            .DependsOn(
                Dependency.OnValue("min", 2),
                Dependency.OnValue("max", 20)));

        container.Register(
            Component.For<INumberToWordConverter>()
            .ImplementedBy<ItalianNumberToWordConverter>());
    }

...
container.Install(new DependenciesConfiguration1());
var testDI = container.Resolve<PlayWithDI>();
testDI.Execute();

The problem arises when I want to give custom parameters to RandomNumberProvider at runtime.

I took a look at TypedFactory, but I don't really understand it in this example, because if I resolve a factory first, how should I then resolve PlayWithDi? Should I pass to its constructor an INumberProviderFactory instead of an INumberProvider?

In this case I thought about a factory like this:

public interface INumberProviderFactory
{
    INumberProvider Create(
        IOutputManager outputManager, 
        int min, int max);
}

When I'll then call Create, how should I resolve the outputManager then? I'm confused.


Solution

  • The problem arises when I want to give custom parameters to RandomNumberProvider at runtime.

    This is where you go wrong. Injecting runtime data into components during construction is ananti-pattern:

    Don't inject runtime data into application components during construction; it causes ambiguity, complicates the composition root with an extra responsibility and makes it extraordinarily hard to verify the correctness of your DI configuration. [...] Let runtime data flow through the method calls of constructed object graphs.

    In other words, your RandomNumberProvider should accept the min and max arguments is input parameters on the GenerateNumber method.