Builder Pattern and Fluent Interface
In the post I want to discuss the practical part of the Builder pattern and how builder pattern usage and implementation can be simplified by Fluent Interface, it will show how these two patterns can leave in harmony with each other.
For how many of us happened that requires enum types to be more complex types, to have a description for each enum or other additional fields. In the description field case we can attach attributes description above each enum value and using reflection to obtain them, in some cases it is a handy solution but in other it’s not. Of course each solution has its limitation, and in many cases enum types are not very helpful.
As another solution, we can create a class with static readonly fields, this could be a typical implementation:
public class EnumType
{
public static readonly EnumType ONE = new EnumType(1, "Descr1");
public static readonly EnumType TWO = new EnumType(2, "Descr2");
public static readonly EnumType THREE = new EnumType(3, "Descr3");
private readonly int id;
private readonly string description;
private EnumType(int id, string description)
{
this.description = description;
this.id = id;
}
public int Id
{
get { return id; }
}
public string Description
{
get { return description; }
}
}
Here are only Id, and Description fields but of course can be more or less depending on the requirements.
Of course, using class instead of enums we will lose other facilities one of them is switch structure usage. To solve last issue I will try to apply Builder pattern. Shortly Builder pattern is a design pattern and its focus is on constructing a complex object step by step. Maybe we will not use it in its “GoF form”, but finally the idea of the patterns is that they are not ready to use solution they are adaptable and should be adapted to the context.
What it will “build” is the switch structure that we can use for class types, similar to above EnumTypes class, but of course our switch usage is not limited only to the type.
Here is a test that will try to pass further:
[Test]
public void CanCreateSimpleSwitchBuilder()
{
EnumType state = null;
var enumType = EnumType.THREE;
var builder = new SimpleSwitchBuilder();
builder.Switch(enumType);
builder.Case(EnumType.ONE);
builder.Body(() => { Console.WriteLine(EnumType.ONE); state = EnumType.ONE; });
builder.Case(EnumType.TWO);
builder.Case(EnumType.THREE);
builder.Body(() => { Console.WriteLine("->" + EnumType.TWO + EnumType.THREE); state = EnumType.TWO; });
builder.Default.DefBody(() => Console.WriteLine("Def"));
builder.Do();
Assert.AreEqual(state, EnumType.TWO);
}
I will not show the SimpleSwitchBuilder code for the test which will pass it because usage of the SimpleSwitchBuilder class is ugly. But the idea is simple, Switch method sets state which will be tested against each Case value, then are Case methods which can cascade and a body represents a action that will be executed when Do method is invoked, if there is no Case for the Switch value then is executed Default action if it is specified.
To increase readability we will introduce fluency for the SimpleSwitchBuilder:
[Test]
public void CanCreateSimpleFluentSwitchBuilder()
{
EnumType state = null;
EnumType enumType = EnumType.THREE;
new SimpleSwitchBuilder()
.Switch(enumType)
.Case(EnumType.ONE)
.Body(() => { Console.WriteLine(EnumType.ONE); state = EnumType.ONE; })
.Case(EnumType.TWO)
.Case(EnumType.THREE)
.Body(() =>
{
Console.WriteLine("->" + EnumType.TWO + EnumType.THREE);
state = EnumType.TWO;
})
.Default
.DefBody(() => Console.WriteLine("Def"))
.Do();
Assert.AreEqual(state, EnumType.TWO);
}
The readability is increased making the each method return itself (this), That is how we introduce the fluency.
Here is the code for the Simple Builder:
public class SimpleSwitchBuilder
{
private Action defaultAction;
private object testObject;
private IList<object> caseList;
private readonly IDictionary<object, Action> caseActions = new Dictionary<object, Action>();
public SimpleSwitchBuilder() { }
public SimpleSwitchBuilder Switch(object obj)
{
caseList = new List<object>();
testObject = obj;
return this;
}
public SimpleSwitchBuilder Case(object obj)
{
caseList.Add(obj);
return this;
}
public SimpleSwitchBuilder Body(Action action)
{
foreach (var switchCase in caseList)
{
caseActions.Add(switchCase, action);
}
caseList = new List<object>();
return this;
}
public SimpleSwitchBuilder Default
{
get { return this; }
}
public SimpleSwitchBuilder DefBody(Action action)
{
defaultAction = action;
return this;
}
public void Do()
{
foreach (KeyValuePair<object, Action> caseAction in caseActions)
{
if (ReferenceEquals(caseAction.Key, testObject) || Equals(caseAction.Key, testObject))
{
caseAction.Value();
return;
}
}
if (defaultAction != null)
defaultAction();
}
}
Ok, fluency is nice, but what if the switch class is not used correctly, and method invocation order is not correct?
[Test]
[ExpectedException(typeof(NullReferenceException))]
public void CanCreateSimpleSwitchBuilderInWrongWay()
{
new SimpleSwitchBuilder()
.Default
.DefBody(() => Console.WriteLine("Def"))
.Case(EnumType.ONE)
.Body(() => Console.WriteLine(EnumType.ONE))
.Do();
}
The exception will rise, but it will not say anything about the problem, to solve the problem we can introduce validation of the methods call order BUT it could become very complex, the validation will be more complex than implementation itself, and the validation will hide the real switch logic.
In order to solve the problem we will introduce interfaces, each interface will return its methods for the next switch step:
public interface IDo
{
void Do();
}
public interface IBody : IDo
{
ICase Case(object obj);
IDefault Default { get; }
}
public interface ICase
{
ICase Case(object obj);
IBody Body(Action action);
}
public interface IDefault
{
IDo Body(Action action);
}
public interface ISwitch
{
ICase Switch(object obj);
}
Implementing the interfaces will allow having following fluency without any complex validation and knowing details of usage of the methods, also the usage is more intuitive.
Instead of many methods that can lead to wrong usage order (see pervious examples):
We will have nice and intuitive usage:
Here is the full source code.
Conclusion
Builder pattern and fluent interface pattern in various scenarios can not only simplify and make more intuitive API usages but also simplify its validation logic. There are other ways of implementation of the fluent interface pattern, for example using nested class.
Thank you,
Artur Trosin