Part 11 - Factories
So most all of us are familiar with factories, and at first glance it seems an IoC container removes the need for having to write our own... but this isn't quite the case - for the container to be able create a component it needs:
- A publicly accessible constructor.
- All dependencies to be registered in the container.
- Mechanisms to allow for configuration and dependencies to be
injected via constructors, or properties.
Facilities are "add-ins" for the container, they can be registered in the container, and augment what the container is capable of doing - there are a lot of facilities for the container, and many of them are documented (to varying degrees) on the castle project wiki ... we will be using a couple of different facilities in this part, first off will be the factory support facility.
So let's start off with a class that's supplied to us by a 3rd party... I'm just going to make one as an example, to keep things simple, called the Sms Service, for sending simple messages to cellular phones...
public class SmsService : ISmsService
{
private SmsConfig _config;public void SetConfig(SmsConfig config)
{
_config = config;
}public void SendMessage(string number, string message)
{
Console.WriteLine("SMS message: {0} sent to: {1} with account: {2}", message, number, _config.UserName);
}public class SmsConfig
{
private string _userName;
private string _password;
private int _retryAttempts;internal string UserName
{
get { return _userName; }
}internal string Password
{
get { return _password; }
}public int RetryAttempts
{
get { return _retryAttempts; }
set { _retryAttempts = value; }
}public void SetCredentials(string userName, string password)
{
_userName = userName;
_password = password;
}
}
}
It also has a corresponding interface:
public interface ISmsService
{
void SendMessage(string number, string message);
}
Now the point is that this class can not be constructred and configured by the container "as-is" because it requires something like this to happen:
SmsService service = new SmsService();SmsService.SmsConfig config = new SmsService.SmsConfig();
config.SetCredentials("joe", "secret");
config.RetryAttempts = 3;service.SetConfig(config);
Ack! Which doesn't fit in nicely with our concepts of constructor and setter injection for configuration... so the answer is first off, to write a factory... can you guess what it'll look like?
public class SmsServiceFactory
{
public ISmsService CreateService()
{
SmsService service = new SmsService();SmsService.SmsConfig config = new SmsService.SmsConfig();
config.SetCredentials("joe", "secret");
config.RetryAttempts = 3;service.SetConfig(config);
return service;
}
}
Well almost - but we don't like hard coding credentials, so we might refactor it a little... to say something like this:
public class SmsServiceFactory
{
private string _userName;
private string _password;
private int _retryAttempts = 3;public SmsServiceFactory(string userName, string password)
{
_userName = userName;
_password = password;
}public int RetryAttempts
{
get { return _retryAttempts; }
set { _retryAttempts = value; }
}public ISmsService CreateService()
{
SmsService service = new SmsService();SmsService.SmsConfig config = new SmsService.SmsConfig();
config.SetCredentials(_userName, _password);
config.RetryAttempts = _retryAttempts;service.SetConfig(config);
return service;
}
}
Now how do we use this factory in lieu of registering the SmsService itself in the container... well first off, we need to add in the factory support facility... which is place within a new part of the castle configuration we haven't seen until now (so far we've only be using the
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
<>
id="factorysupport"
type="Castle.Facilities.FactorySupport.FactorySupportFacility, Castle.MicroKernel" />
Nothing much to it, but now the factory support facility has augmented our container, so we can do this:
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
<>
id="factorysupport"
type="Castle.Facilities.FactorySupport.FactorySupportFacility, Castle.MicroKernel" />
type="IoC.Tutorials.Part11.SmsServiceFactory, IoC.Tutorials.Part11">joe secret
type="IoC.Tutorials.Part11.ISmsService, IoC.Tutorials.Part11"
factoryId="smsService.Factory"
factoryCreate="CreateService" />
Notice that we've registered our factory as a component, and called it smsService.Factory - and then we've registered the smsService.default (interface only) and added a couple of additional attributes, factoryId (which references our factory component) and factoryCreate - the name of the method in the factory which is responsible for creating instances of our sms service.
Because we now have the factory support facility installed, whenever a component is registered that facility will inspect it and see if the factoryId attribute appears in the components configuration and consequently defer it's activation to the referenced factory - pretty cool huh?
So, the million dollar question, does it work? Well lets see... here's our code:
static void Main(string[] args)
{
WindsorContainer container = new WindsorContainer(new XmlInterpreter());ISmsService smsService = container.Resolve
();
smsService.SendMessage("+465556555", "testing testing...1.2.3");Console.Read();
}
And the output:
SMS message: testing testing...1.2.3 sent to: +465556555 with account: joe
At this point I was going to take a quick look into another factory-related facility, the TypedFactory, but it's currently broken in the main trunk ;o) so we'll wait until that's been resolved before finishing off this post.
Next time we'll have a quite look at revisiting a class design pattern, the decorator, when using the Windsor container.