Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a Castle Windsor typed factory return the same instance when creating with different parameters

I would expect the following to produce two separate instances when using the typed factory facility.

using System;
using Castle.Facilities.TypedFactory;
using Castle.MicroKernel.Registration;
using Castle.Windsor;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var container = new WindsorContainer();

            container.AddFacility<TypedFactoryFacility>();

            container.Register(Component
                .For<IFactory>()
                .AsFactory()
                .LifestyleSingleton());

            container.Register(Component
                .For<IImplementation>()
                .ImplementedBy<Implementation>()
                .LifestylePerThread());

            var factory = container.Resolve<IFactory>();
            var implementation1 = factory.Create(1);
            var implementation2 = factory.Create(2);

            Console.WriteLine(implementation1 == implementation2);//Returns true!
            Console.Read();
        }
    }

    public interface IFactory
    {
        IImplementation Create(int dependency);
    }

    public interface IImplementation
    {}

    public class Implementation : IImplementation
    {
        private readonly int _dependency;

        public Implementation(int dependency)
        {
            _dependency = dependency;
        }
    }
}

I've also tried it with the parameter as a reference type that overrides .Equals() and .GetHashCode() instead of an int but it makes no difference.

I realise I can use LifestyleTransient to solve this problem but I would actually want to receive the same instance if I pass in the same parameter.

like image 588
user3432422 Avatar asked Jun 13 '16 13:06

user3432422


2 Answers

Your expectation is incorrect.

The parameters passed to the factory method are the details that will be used to construct a new component if, and only if, there is not already a component of the required service available in the container.

Your second request is made from the same thread and is for the same service so Windsor is correctly returning the one that was already constructed.

While Gilad's suggestion is a possible one to follow, you may still find yourself 'fighting' the container and making things more complex than they need to be.

I would suggest that you embrace the mechanisms available in Windsor, which rely completely on service types (interfaces) to differentiate services from one another.

Ask yourself what it is about the two instances that is different and reflect those differences in terms of interfaces. e.g. maybe you should have an IBigImplementation and ISmallImplementation? The implementations of these different services can then be registered and configured in the container; you get all the pooling/reuse you intend; and consuming code remains blissfully unaware and decoupled from the implementation details.

[RANT: While factories allow for much greater flexibility, generally I regard the use of parameters to factory methods as a code smell. As you've discovered, it requires the consumer to make assumptions about the lifecycle of the service implementation. It also means that the levers and switches that control implementation details are scattered through the code base rather than all being managed centrally in container registration code.]

like image 58
Phil Degenhardt Avatar answered Oct 04 '22 22:10

Phil Degenhardt


From what I know about Castle the TypedFactoryFacility will try and resolve for you the type according to the factory's interface. If you have a function like IImplementation Create(int dependency) then it will try to resolve from the Kernel an object of type IImplementation. That is why you get the same one when you have them registered with Singelton.

What you are actually looking for is a like of "TypedFactory" that will return an instance not by the type but by the instance of an object you have. What you can do is implement an ITypedFactoryComponentSelector that will try to resolve from the kernel and IImplementation that also has that int dependency you passed, and if one does not exist register one before returning it.

You can look here for deeper insights on the TypedFactory and on implementing your own ITypedFactoryComponentSelector: https://github.com/castleproject/Windsor/blob/master/docs/typed-factory-facility-interface-based.md

Hope this helps

like image 22
Gilad Green Avatar answered Oct 04 '22 21:10

Gilad Green