Interface-based typed factories are extremely useful in following situation:
Object needs dependency provided after it was created:
An example of a component like that would be a message dispatcher which waits for a message to arrive, then when that happens it pulls appropriate message handler and delegates the handling of the message to it. Using a typed factory the dispatcher can easily pull message handlers from the container, without having to explicitly use it. This gives you full power of the container and keeps your code expressive and free from service location.
Once you have created a factory interface:
public interface IDummyComponentFactory
{
IDummyComponent Create();
void Release(IDummyComponent dummyComponent);
}
you can then register this with the Windsor framework using the extension method AsFactory
:
kernel.AddFacility<TypedFactoryFacility>();
kernel.Register(
Component.For<IDummyComponentFactory>()
.AsFactory()
);
(This requires the namespace Castle.Facilities.TypedFactory
)
ℹ️ That's right - no implementation: Notice we specify an interface and don't provide any implementation. By using AsFactory
extension method we're telling Windsor to provide its own implementation that adheres to the default convention for typed factories (discussed below)
Not just any type may be used as typed factory. The following requirements must be met:
- The type must be an interface
- None of its methods can have out parameters.
You use of typed factories much like you would use any other service. There are three kinds of methods it treats specially:
- methods with no return value (having 'void' as return type)
- methods with return value (having something different than 'void' as return type)
Dispose
method (if typed factory implementsIDisposable
)
We will now go over them in turn.
Methods with non-void return type are used for supplying components to the caller. Say we register a component and a factory that will be used to create it.
kernel.Register(
Component.For<IDummyComponent>()
.ImplementedBy<Component2>()
.Named("SecondComponent")
.LifeStyle.Transient,
Component.For<IDummyComponentFactory>()
.AsFactory());
now we can resolve the factory:
var factory = kernel.Resolve<IDummyComponentFactory>();
and someplace else in the code we can use the factory to give us the component we need
var component = factory.GetSecondComponent();
You can also use methods that take parameters from the caller to resolve components. The argument you pass in, will be passed on to the container's resolution pipeline.
kernel.Register(
Component.For<ICalendarFactory>().AsFactory(),
Component.For<ICalendar>().ImplementedBy<Calendar>().LifeStyle.Transient
);
Now you can resolve the factory
var calendarFactory = kernel.Resolve<ICalendarFactory>();
And use it passing some arguments in.
var today = DateTime.Today;
var calendar = calendarFactory.CreateCalendar(today);
Debug.Assert(calendar.Today == today);
ℹ️ How arguments are bound: Specifics about how method being invoked and its arguments are bound to resolved component are determined by an implementation of ITypedFactoryComponentSelector
interface. By default the facility uses DefaultTypedFactoryComponentSelector
(discussed below) but you can supply your own to customize its behavior.
Windsor always tracks disposable non-singleton components resolved via typed factory. That means that if you resolve a component from the container you should also release it as soon as you're done using it, to allow Garbage Collector to reclaim memory and resources occupied by it. How do you do it when you're using Typed Factory? With Releasing methods.
Releasing methods are counterparts to Resolving methods. They release components resolved by the latter. Any "void
method" other than Dispose
is a releasing method (actually Dispose
is a releasing method as well, just a very special one). It tries to release all objects passed to them as parameters.
Given you resolved a component from a typed factory:
var component = factory.Create();
You can then pass it as an argument to any of factory's void methods to have the factory release the component from the container
factory.Destroy(component);
You can also pass more components at once, and the facility will release them in turn.
ℹ️ Releasing the factory releases all components: Typed factory (both interface
and delegate
-based) owns the components you resolve through it. That means that when you release the factory, all the components you resolved from the factory will be released as well.
When your typed factory interface implements IDisposable
it gains a powerful ability - disposing the factory releases all components created via the typed factory
var factory = kernel.Resolve<IDisposableFactory>();
var component = factory.Create();
Debug.Assert(component.Disposed == false);
Now you can dispose the factory, and all non-singleton components will be released as well.
factory.Dispose();
Debug.Assert(component.Disposed == true);
Typed Factory Facility uses implementation of ITypedFactoryComponentSelector
interface to decide how information it received from the factory (factory Resolving method and its arguments) should be mapped to information forwaded to the container to perform actual resolution of the component. The facility comes with one implementation: DefaultTypedFactoryComponentSelector
but if you need custom behavior you can supply your own.
DefaultTypedFactoryComponentSelector
obeys few conventions:
When you have a factory method with one of the following return types:
IFoo[][]
IEnumerable<IFoo>
ICollection<IFoo>
IList<IFoo>
The factory will recognize that you want a collection, not a single component and it will return collection of all components for service IFoo
rather than single component.
For example, given the factory interface:
public interface IMessageHandlersFactory
{
IMessageHandler[] GetAllHandlersForMessage(IMessage message);
}
calling the method:
var handlers = factory.GetAllHandlersForMessage(new HelloWorldMessage());
is direct equivalent of calling:
var handlers = container.ResolveAll<IMessageHandler>(new { message = new HelloWorldMessage()});
When you have a factory method named GetSomething
, selector will ask the container for a service named Something
.
kernel.Register(
Component.For<IDummyComponent>()
.ImplementedBy<Component2>()
.Named("SecondComponent")
.LifeStyle.Transient
);
Now if we have a method called GetSecondComponent
:
var component = factory.GetSecondComponent();
Call to the factory's method is direct equivalent of calling:
var component = kernel.Resolve<IDummyComponent>("SecondComponent");
For all other methods, the return type of the method is used as type of component to look-up, so call to factory method:
var component = factory.CreateSecondComponent();
is direct equivalent of calling: (assuiming IDummyComponent
is the return type of the method)
var component = kernel.Resolve<IDummyComponent>();
When factory method has parameters, their names and values are forwarded to the kernel, so call to the following typed factory method:
IComponent CreateComponent(string componentName, int someParameter);
var component = factory.CreateComponent("foo", 3);
is direct equivalent of calling:
var component = kernel.Resolve<IComponent>(new Dictionary<string, object>`"componentName", "foo"}, {"someParameter", 3`);
ℹ️ Utilize DefaultTypedFactoryComponentSelector
: When creating your custom ITypedFactoryComponentSelector
consider inheriting DefaultTypedFactoryComponentSelector
instead of implementing the interface directly. DefaultTypedFactoryComponentSelector
is designed with extensibility in mind, so chances are, overriding single virtual method with few lines of code will get you what you need.
This custom implementation resolve component by id when a method with the following signature is called on the factory:
factory.GetById(string)
Here is the implementation:
public class CustomTypedFactoryComponentSelector : DefaultTypedFactoryComponentSelector
{
protected override string GetComponentName(MethodInfo method, object[] arguments)
{
if (method.Name == "GetById" && arguments.Length == 1 && arguments[0] is string)
{
return (string)arguments[0];
}
return base.GetComponentName(method, arguments);
}
}
And the configuration:
var ioc = new WindsorContainer();
ioc.AddFacility<TypedFactoryFacility>();
ioc.Register(Component.For<ITypedFactoryComponentSelector>().ImplementedBy<CustomTypedFactoryComponentSelector>());
The facility will use default ITypedFactoryComponentSelector
unless you explicitly override it for your factories (on a one-by-one basis).
You can specify the selector explicitly in three ways:
If you don't want to register the selector as a service in your container, you can explicitly provide an instace.
container.Register(
Component.For<DummyComponentFactory>().AsFactory(c => c.SelectedWith(new MyCustomSelector()))
);
This is useful in one-case scenario where you want to use a non-default selector for a single factory among many.
When your selector is registered as a service in the container, you can specify it by type
container.Register(
Component.For<DummyComponentFactory>().AsFactory(c => c.SelectedWith<MyComponentSelector>()),
Component.For<MyComponentSelector, ITypedFactoryComponentSelector>()
);
When your selector is registered as a service in the container, you can specify it by name as well.
container.Register(
Component.For<DummyComponentFactory>().AsFactory(c => c.SelectedWith("factoryTwo")),
Component.For<ITypedFactoryComponentSelector>().ImplementedBy<MyComponentSelector>().Named("factoryOne")
);
In addition to code the facility exposes also XML configuration.
<configuration>
<facilities>
<facility type="Castle.Facilities.TypedFactory.TypedFactoryFacility, Castle.Windsor">
<factories>
<factory id="handlerFactory"
interface="Acme.Crm.IHandlerFactory, Acme.Crm" selector="${mySelectorComponent}" />
<factory id="repositoryFactory"
interface="Acme.Crm.IRepositoryFactory, Acme.Crm" />
</factories>
</facility>
</facilities>
</configuration>
ℹ️ Configuration goes first: When using XML configuration the configuration must be installed before you add the facility to the container. Otherwise the facility will not read the configuration.