Last week I had a nice discussion in the office about “how to write fluent interface” and I have found a couple of articles over the internet about that. As usual I disagree with some of them and, as usual, my friend Mauro Servienti (MVP C#) has a nice article about it, unfortunately in Italian. He just gave me the startup input.
If you think about the Fluent Interface, it is just a trick that you use with C# in order to cheat the intellisense of Visual Studio and in order to create a nice style for your code. Of course there is a specific way to write fluent interface.
Let’s make a short sample. The classic Person class and the factory pattern.
We have a class which represents the Person Entity and has some common properties.
Very easy. Now, in a normal world you would have something like this, in order to create a new instance of a class person.
First way, the classis way:
Classic Person Factory
- public class PersonFactory
- {
- public static Person CreatePerson(string firstName, string middleName, string lastName)
- {
- var person = new Person
- {
- FirstName = firstName,
- MiddleName = middleName,
- LastName = lastName
- };
- return person;
- }
- }
Create Person
- var person = PersonFactory.CreatePerson("Raffaele", string.Empty, "Garofalo");
Now, if we want to add an address to this person we need an additional like of code like this one, and of course a new factory for the class person or a new method in the person factory … whatever …
Create Address
- var address = PersonFactory.CreateAddress("1st Lane", "Main Road", "Hamilton", "Bermuda", "HM10");
- person.Addresses.Add(address);
First of all, here we have a big problem. All the parameters are strings. So, if we don’t explain in a proper verbose way each parameter, the developer that will use our factory won’t be able to know what to write in each parameter. Second thing, if we don’t use C# 4 we have to specify each parameter value anyway … Finally we are avoiding a nice readability in our code.
The first step for a fluent interface.
I saw a lot of code around the web but a lot of people forget about the name of this technique … The name is Fluent Interface so this means that probably we should add some interfaces in our code in order to have a good result. Well VS 2010 is able to create an interface from a class just with a couple of clicks … And this is the final result:Now we need to translate each method in a Fluent method. Let’s start with the class person. What I am doing is an interface for my Factory and two methods, one for the Factory initialization, where we initialize a new instance of the class person and one to finalize it, where we will return the current instance of that class. Of course we need also the methods to add some values to the person properties. Look at the UML:
At here is the code:
IPersonFactory
- public interface IPersonFactory
- {
- IPersonFactory Initialize();
- IPersonFactory AddFirstName(string firstName);
- IPersonFactory AddLastName(string lastName);
- IPersonFactory AddMiddleName(string middleName);
- IPerson Create();
- }
And this is the implementation:
PersonFactory
- public class PersonFactory : IPersonFactory
- {
- private IPerson person = null;
- public IPersonFactory Initialize()
- {
- person = new Person();
- return this;
- }
- public IPersonFactory AddFirstName(string firstName)
- {
- person.FirstName = firstName;
- return this;
- }
- public IPersonFactory AddLastName(string lastName)
- {
- person.LastName = lastName;
- return this;
- }
- public IPersonFactory AddMiddleName(string middleName)
- {
- person.MiddleName = middleName;
- return this;
- }
- public IPerson Create()
- {
- return person;
- }
- }
So now we start to have a FluentInterface capability in our code.
Code Snippet
- var person = new PersonFactory()
- .Initialize()
- .AddFirstName("Raffaele")
- // we can skip this line now ...
- .AddMiddleName(string.Empty)
- .AddLastName("Garofalo")
- .Create();
Very well done but we still have a problem here. We are not giving a constraint to the developer that will use our fluent syntax. Let’s say that we are working with a retarded colleague, nobody can prohibit him to write something like this:
Wrong Person
- var wrongPerson = new PersonFactory().Create();
In this case he will get a nice NullReferenceException because if he doesn’t call the method Initialize the factory won’t create a new instance of the class person … So how can we add a constraint to our interface? Very simple, we need 3 interfaces and not only one anymore. We need IInitFactory, IPersonFactory and ICreateFactory.
Let’s see the code:
IPersonFactory
- public interface IPersonFactory
- {
- IPersonFactory AddFirstName(string firstName);
- IPersonFactory AddLastName(string lastName);
- IPersonFactory AddMiddleName(string middleName);
- IPerson Create();
- }
The IPersonFactory now will not be in charge anymore of creating a new instance of the class person, it will just be in charge of working with it. We will use dependency injection to inject a new instance. Let’s the concrete implementation of this factory:
Person Factory refactored
- public class PersonFactory : IPersonFactory
- {
- private IPerson person = null;
- public PersonFactory(IPerson person)
- {
- this.person = person;
- }
- public IPersonFactory AddFirstName(string firstName)
- {
- this.person.FirstName = firstName;
- return this;
- }
- public IPersonFactory AddLastName(string lastName)
- {
- this.person.LastName = lastName;
- return this;
- }
- public IPersonFactory AddMiddleName(string middleName)
- {
- this.person.MiddleName = middleName;
- return this;
- }
- public IPerson Create()
- {
- return this.person;
- }
- }
Now we need an orchestrator. Somebody that will be visible outside and will be in charge of giving to the fluent syntax a static flavor (we want to avoid the new Factory() syntax …) and that will return a PersonFactory ready to work …
Person Fluent Factory
- public static class PersonFluentFactory
- {
- public static IPersonFactory Init()
- {
- return new PersonFactory(new Person());
- }
- }
And now Visual Studio will follow our rules …
Final Step, Lamba expression for a cool syntax.
Ok this is cool and it works like we want but … it is really time consuming. We want a fluent interface and that’s fine but if you a domain with 100 entities and more or less 100 factories, can you imagine the pain in the neck in order to adopt this pattern all over?? Well this is the reason you should study more in depth C#!! If you didn’t know, there is a cool syntax future in C# called Lambda Expressions. If you don’t know what I am talking about, have a look here.First of all we need a generic interface for our factory with only two methods, one to add a value to a property and one to return the current instance of the entity created by the factory.
IGenericFactory
- public interface IGenericFactory<T>
- {
- IGenericFactory<T> AddPropertyValue(Expression<Func<T, object>> property, object value);
- T Create();
- }
Then following the same logic of the previous steps we need a concrete implementation but using generics and lambda expressions (I really love this part).
Generic Factory <T>
- public class GenericFactory<T> : IGenericFactory<T>
- {
- T entity;
- public GenericFactory(T entity)
- {
- this.entity = entity;
- }
- public IGenericFactory<T> AddPropertyValue(Expression<Func<T, object>> property, object value)
- {
- PropertyInfo propertyInfo = null;
- if (property.Body is MemberExpression)
- {
- propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;
- }
- else
- {
- propertyInfo = (((UnaryExpression)property.Body).Operand as MemberExpression).Member as PropertyInfo;
- }
- propertyInfo.SetValue(entity, value, null);
- return this;
- }
- public T Create()
- {
- return this.entity;
- }
And the Fluent Factory, now generic, in this way:
Code Snippet
- public static class GenericFluentFactory<T>
- {
- public static IGenericFactory<T> Init(T entity)
- {
- return new GenericFactory<T>(entity);
- }
- }
The final syntax will be this one:
Code Snippet
- var person = GenericFluentFactory<Person>
- .Init(new Person())
- .AddPropertyValue(x => x.FirstName, "Raffaele")
- .AddPropertyValue(x => x.LastName, "Garofalo")
- .Create();
Ta da!! One generic factory with syntax constraints to create as many entities as you want! And the smart developer that is working with you won’t be able to complain at all.
Final notes.
We can make this code better by:- Adding a custom type reflected by the property we are using, so you won’t be able to add an integer value to a property of type string and so on …
- Remove that ugly .Init(new Person()) using an IoC engine or a new constraint to our class, of course in this case you must provide classes with at least 1 parameters less constructor.
Another Small one below
public static readonly ILog logger = LogManager.GetLogger(typeof(ErrorLogger));
public interface ILog : ILoggerWrapper{
bool IsDebugEnabled { get; }bool IsErrorEnabled { get; }bool IsFatalEnabled { get; }bool IsInfoEnabled { get; }bool IsWarnEnabled { get; }void Debug(object message);void Debug(object message, Exception exception);void DebugFormat(string format, object arg0);void DebugFormat(string format, params object[] args);void DebugFormat(IFormatProvider provider, string format, params object[] args);void DebugFormat(string format, object arg0, object arg1);void DebugFormat(string format, object arg0, object arg1, object arg2);void Error(object message);void Error(object message, Exception exception);void ErrorFormat(string format, object arg0);void ErrorFormat(string format, params object[] args);void ErrorFormat(IFormatProvider provider, string format, params object[] args);
}
public interface ILoggerWrapper{
}
ILogger Logger { get; }
oid Info(object message, Exception exception);void InfoFormat(string format, object arg0);void InfoFormat(string format, params object[] args);void InfoFormat(IFormatProvider provider, string format, params object[] args);void InfoFormat(string format, object arg0, object arg1);void InfoFormat(string format, object arg0, object arg1, object arg2);void Warn(object message);void Warn(object message, Exception exception);void WarnFormat(string format, object arg0);void WarnFormat(string format, params object[] args);void WarnFormat(IFormatProvider provider, string format, params object[] args);void WarnFormat(string format, object arg0, object arg1);void WarnFormat(string format, object arg0, object arg1, object arg2);
Comments
Post a Comment