Mauricio Scheffer asked how AutoGen differs from the TypedFactory Facility already in Castle. (An equally valid question is why the code is five times the size.) The answer is that it doesn’t in essence, but it does in detail. However, the details matter, in that AutoGen addresses common use cases, whereas the TypedFactoryFacility is only going to save you 6 lines of code. The principal differences are:
- Configuration
- Constructor Arguments
- Handling Keys
- Disposal
- Handling Multiple Methods
How TypedFactoryFacility is used
Let’s take a look at how you’d implement the example on the AutoGen home page using the TypedFactoryFacility
[Test]
public void TutorialConvertedToTypedFacility()
{
var container = new WindsorContainer();
var facility = new TypedFactoryFacility();
container.AddFacility("TypedFactory", facility);
facility.AddTypedFactoryEntry(new FactoryEntry("X", typeof(IFactory), "CreateExample", null));
container.Register(Component.For<IExample>().ImplementedBy<Example>());
var factory = container.Resolve<IFactory>();
Assert.IsTrue(factory.CreateExample() is Example);
}
OK, let’s observe a couple of things. First, we created the facility. AutoGen is implemented the same way, as a facility called AutoGenFacility. However, the next line is where the two begin to diverge. With the TypedFactoryFacility, you add the interfaces you want directly to the facility, rather than by adding configuration attributes to the components. This means that it has its own syntax, whilst AutoGen just expects you to add “ccAutoGen=’true'” in your XML files, or “@ccAutoGen=’true'” in your Binsor file. This is the way the RemotingFacility works. The downside of this approach is that it’s harder to use with fluent configuration, which is why I provided an extension method directly for that use case.
(The integration of the AutoGen syntax directly into the registration mechanism is, of course, via a mechanism (extension methods) that didn’t exist when the TypedFactoryFacility was created.)
So far, the two facilities are pretty similar, except for the fact that the TypedFactoryFacility requires you to tell it the create and release methods.
Supporting the Abstract Factory Pattern
There is, of course, a difference between a Factory pattern and an Abstract Factory pattern. The factory pattern is basically just a method which creates an object. Let’s remind ourselves of the GoF maze abstract factory:
- MakeMaze()
- MakeWall()
- MakeRoom(int n)
There are rwo important observations here. The first is that there is more than one creation method. The typed factory facility can’t do this, and it can’t do it by the design of the API: by asking for a single creation method, it can’t support interfaces with more than one. Second, the MakeRoom method takes a parameter. Often when dealing with object creation, there are parameters that vary at runtime. Castle supports this, but the typed factory does not. The following code demonstrates this:
[Test]
[ExpectedException(typeof(InvalidCastException))]
public void TypedFacilityCantImplementConstructorParameters() {
var container = new WindsorContainer();
var facility = new TypedFactoryFacility();
container.AddFacility("TypedFactory", facility);
facility.AddTypedFactoryEntry(new FactoryEntry("X", typeof(IFactory2), "CreateExample2", null));
container.Register(Component.For<IExample2>().ImplementedBy<Example2>());
var factory = container.Resolve<IFactory2>();
var result = factory.CreateExample2(999);
Assert.That(result.Value, Is.EqualTo(999));
}
[Test]
public void AutoGenCanImplementConstructorParameters() {
var container = new WindsorContainer();
container.Register(Component.For<IExample2>().ImplementedBy<Example2>());
var factory = container.AutoGen<IFactory2>();
var result = factory.CreateExample2(999);
Assert.That(result.Value, Is.EqualTo(999));
}
This isn’t tricky to support (Castle’s got all of the hooks you need), but it requires about five times the code of the existing TypedFactoryFacility, since you’ve got to support various use cases for how you’d like to map the parameters to constructor parameters. Sadly, Castle’s support here isn’t quite as deep as I’d like, but that’s a subject for another day.
For reference, here is the IExample2 code
public interface IExample2 { int Value { get; } }
public class Example2 : IExample2 {
private readonly int value;
public Example2(int value) {
this.value = value;
}
public int Value {
get { return value; }
}
}
Supporting IDisposable
The TypedFactory facility does it’s job and leaves. If you want to release the object from Castle, it’s got the ability to declare a release method, but that’s it. AutoGen actually goes further, in two ways:
- Any object returned that implements IDisposable will be released from Castle when the dispose method is called.
- Any factory that implements IDisposable will release all objects it has generated when it is disposed.
To illustrate, here’s the test that a) Release is called and b) the original disposal is called.
[Test]
public void DisposeReleasesASingleton()
{
var factory = container.Resolve<IFactory>();
var singleton = factory.Create();
Assert.That(factory.Create(), Is.EqualTo(singleton));
Assert.That(singleton.DisposeCount,Is.EqualTo(0));
singleton.Dispose();
Assert.That(singleton.DisposeCount,Is.EqualTo(1));
Assert.That(factory.Create(),Is.Not.EqualTo(singleton));
}
Of course, doing this means that you need to be careful with lifetimes, hence such tests as “SingletonsAreSingletonsWhenProxied”.
Again, this makes the code longer and more detailed than the TypedFactoryFacility.
Conclusion
I hope I’ve managed to demonstrate why the AutoGen code is different from the TypedFactoryFacility. I hope I’ve also described why I think the concept is important. A valid question, however, is why I haven’t submitted the work to the Castle dev team yet. The answer to that one is less good: I didn’t think there would be a lot of interest (or someone would have already done it). I should, however, verify that.
As an aside, the Dynamic Proxy (without which none of this would work) code in Castle is extremely wonderful and powerful. This was my first project that used it, but it won’t be the last. I honestly do not believe it should be wasted on AOP.
I agree that the NHibernateFacility would be the right place to implement this. Not so sure about subclassing, maybe just a flag would be enough to enable Fabio’s DI BytecodeProvider. And the facility would provide something like a IEntityFactory with Create() (implemented by AutoGen) to be injected wherever you need to create a new nhibernate entity. Also take a look at code.google.com/…/EntitiesAbstrDI , it seems that Fabio has been experimenting with LinFu’s container integration.Try running this through the dev list, let’s see the experts’ opinion 🙂
LikeLike