Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would you use Windsor AsFactory?

Why would you use Castle Windsor factory auto implementation feature: AsFactory() rather then asking for needed interface?

Example:

container.Register(Component.For<IEmailSender>().ImplementedBy<SmtpEmailSender>());
container.Register(Component.For<IEmailSenderFactory>().AsFactory().LifeStyle.Transient);

...

using (var factory = context.GetServiceFactory<IEmailSenderFactory>())
{
    var emailSender = factory.CreateEmailSender();
    emailSender.Send(message);
}

Why wouldn't you simply write:

var emailSender = context.GetServiceFactory<IEmailSender>();
emailSender.Send(message);

Effect is the same. Windsor will resolve IEmailSender as default registered implementation anyway so what is the point?

like image 474
mswietlicki Avatar asked Jul 31 '14 15:07

mswietlicki


1 Answers

1. To inject specific constructor arguments

Sometimes you'll write a class that requires a specific value when you need to resolve it. For instance:

public class NumberWriter : INumberWriter
{
    readonly int number;
    readonly IStream stream;

    public NumberWriter(int number, IStream stream)
    {
        this.number = number;
        this.stream = stream;
    }

    public Write()
    {
        stream.Write(number);
    }
}

You can't resolve an instance of this class without a number, and maybe you'd also like to specify the stream (console, file, printer, whatever). So, you define a factory:

public interface INumberWriterFactory
{
    INumberWriter Create(int number);
    INumberWriter Create(int number, IStream stream);
} 

Now the following code will work:

public class RandomNumberGenerator
{
    readonly INumberWriterFactory numberWriterFactory;

    public RandomNumberGenerator(INumberWriterFactory numberWriterFactory)
    {
        this.numberWriterFactory = numberWriterFactory;
    }

    public void Generate()
    {
         Random random = new Random();
         for (int i = 0; i < 10; i++)
         {
             // Writes to first IStream that Castle can resolve
             var numberWriter = numberWriterFactory.Create(random.Next());
             numberWriter.Write();
         }
    }

    public void Generate(IStream stream)
    {
         Random random = new Random();
         for (int i = 0; i < 10; i++)
         {
             // Writes to the given IStream
             var numberWriter = numberWriterFactory.Create(random.Next(), stream);
             numberWriter.Write();
         }
    }
}

2. To introduce a level of abstraction

Using a factory can insulate you from changes to how you'll need to create an object. For instance, if you'll need to create instances of objects and always use the same constructor parameter each time, you can create a concrete factory and then just use that everywhere instead of the one generated via AsFactory().

In other words, we could modify the behavior some of the code by burying the stream parameter in the factory so that a specific default stream is always used (for instance if an IStream cannot just be resolved from the container). Doing it this way means we wouldn't need to change the RandomNumberGenerator at all:

public class NumberWriterFactory : INumberWriterFactory
{
    readonly IStream stream;
    readonly IContainer container;

    public NumberWriterFactory(IStream stream, IContainer container)
    {
        this.stream = stream;
        this.container = container;
    }

    public INumberWriter Create(int number)
    {
        return container.Resolve<INumberWriter>(number, this.stream);
    }

    public INumberWriter Create(int number, IStream stream)
    {
        return container.Resolve<INumberWriter>(number, stream);
    }
}

And no change in RandomNumberGenerator, but the behavior is changed:

public class RandomNumberGenerator
{
    readonly INumberWriterFactory numberWriterFactory;

    public RandomNumberGenerator(INumberWriterFactory numberWriterFactory)
    {
        this.numberWriterFactory = numberWriterFactory;
    }

    public void Generate()
    {
         Random random = new Random();
         for (int i = 0; i < 10; i++)
         {
             // Writes to the IStream instance that the factory contains
             var numberWriter = numberWriterFactory.Create(random.Next());
             numberWriter.Write();
         }
    }  

    // the rest as before
}

Again, this is useful in the sense that if you were already using a factory interface, such as one implemented by using AsFactory(), you could easily swap it out for a new implementation. Doing this if you were already using a container instead is more difficult; it's harder to find the places you need to change and it's harder to swap out usage of the container to use a new type (i.e. a factory).

Note: you would have to create an INumberWriterFactoryFactory to inject the IStream into the concrete factory.

3. To keep usage of the IOC container in the composition root

There's a lot of folk who subscribe to the idea that there is only one composition root, and it is the only time a reference to the IOC container is permitted. Doing so can help you to avoid several anti-patterns, such as the service locator pattern.

Please note that these examples are dumb, but I hope they get the points across.

like image 68
Patrick Quirk Avatar answered Oct 16 '22 22:10

Patrick Quirk