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();
}
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