Francesco B. Francesco B. - 3 months ago 25
C# Question

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.

Answer

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.

Comments