Creating a simple factory for objects with a fluent configuration API
When I recently came to writing a new component for a system I thought, "I love using fluent configuration in other libraries so I'm going to write this with a fluent configuration API!". Of course, as with most projects, I made a lot of mistakes and had to keep going back and rewriting it from the ground up.
In order to actually use an IFactory, we need a way of building them. To keep the IFactory interface small, I wanted a separate class responsible for configuring a factory and then creating instances based on that configuration:
So what do the implementations of these interfaces look like?
The factory builder keeps track of all of the member mappings that have been added by adding them/updating a dictionary which is then passed to the SimpleFactory constructor when you call compile. Returning `this` from the ForMember method allows you to chain multiple calls.
I also wasn't able to find a lot of examples/discussion on creating fluent APIs for configuration so I decided to write a simple example that isn't really useful in itself but is useful as a learning exercise (at least it was for me!)
I decided to put the full source on Github rather than uploading a zip as I find downloading examples as a zip annoying!
The whole idea for the example was to have a simple way of creating a factory object that is able to create objects of a certain type according to some configured rules. To make life easier, I added a constraint to only allow objects with a parameterless constructor.
Or to put it another way:
public interface IFactory<out T> where T : class, new()
{
T Build();
}
In order to actually use an IFactory, we need a way of building them. To keep the IFactory interface small, I wanted a separate class responsible for configuring a factory and then creating instances based on that configuration:
public interface IFactoryBuilder<T> where T : class, new()
{
IFactoryBuilder<T> ForMember<TProp>(
Expression<Func<T, TProp>> accessor,
Func<T, TProp> result);
IFactory<T> Compile();
}
The signature may look scary, but all I'm saying is that I want people to specify access to a member and a function that returns a value for that member like:IFactoryBuilderInstance<T>.ForMember(t => t.SomeStringProp, t => "Constant");
So what do the implementations of these interfaces look like?
public class FactoryBuilder<T> : IFactoryBuilder<T> where T : class, new()
{
private readonly IDictionary<string, Delegate> _evaluators =
new Dictionary<string, Delegate>();
public IFactoryBuilder<T> ForMember<TProp>(
Expression<Func<T, TProp>> accessor,
Func<T, TProp> result)
{
_evaluators[ExpressionHelper.GetPropertyName(accessor)] = result;
return this;
}
public IFactory<T> Compile()
{
return new SimpleFactory<T>(_evaluators);
}
}
The factory builder keeps track of all of the member mappings that have been added by adding them/updating a dictionary which is then passed to the SimpleFactory constructor when you call compile. Returning `this` from the ForMember method allows you to chain multiple calls.
class SimpleFactory<T> : IFactory<T> where T : class, new()
{
private IDictionary<string, Delegate> _evaluators;
public SimpleFactory(IDictionary<string, Delegate> _evaluators)
{
this._evaluators = _evaluators;
}
public T Build()
{
var result = new T();
var type = typeof (T);
var props = type.GetProperties();
foreach (var e in _evaluators)
{
var propInfo = props.FirstOrDefault(p => p.Name == e.Key);
var value = e.Value.DynamicInvoke(result);
if (propInfo != null) propInfo.SetValue(result, value);
}
return result;
}
}
The implementation of these isn't really that important - it's just a very naive way of creating an object with reflection.
So what does using it look like?
Which produces:
So what does using it look like?
public class Program
{
static void Main(string[] args)
{
IFactoryBuilder<Test> builder = new FactoryBuilder<Test>();
var rand = new Random();
// The result gets built in the order that the specifications were added.
// This means that it's fine for later specs to use already specced values.
var testFactory = builder.ForMember(t => t.A, t => "A is always the same")
.ForMember(t => t.B, t => rand.Next(0, 100))
.ForMember(t => t.C, t => t.B.HasValue && t.B > 50)
.ForMember(t => t.D, t => "but B is random and C is based on B!")
.Compile();
Console.WriteLine(testFactory.Build().ToString());
Console.WriteLine(testFactory.Build().ToString());
Console.WriteLine(testFactory.Build().ToString());
Console.WriteLine(testFactory.Build().ToString());
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
Which produces:
Obviously this factory leaves a bit to be desired - recursion will kill it, if you don't map a member there are no default actions but hopefully it's a useful example on one way to create a fluent configuration API.
Comments
Post a Comment