I've recently finished my review of NHibernate's mapping-by-code feature and the thing I'm most impressed with is its API design. Fabio Maulo, mapping-by-code creator, calls this a loquacious interface, as opposed to chained, fluent interface. I don't know if that name is well established or formalized yet - Google shows only NH-related hits. I don't know any other projects using solely this kind of API either. But I think this is going to change soon, as Fabio's approach seems to be more powerful and in a lot of cases more readable and "fluent" than chained interfaces.
What exactly I'm talking about?
I'm thinking of an API intended to build complex structures in code that resembles the structure itself. Mapping-by-code API (loosely) resembles NHibernate's HBM XML structure, so that when in XML we had an attribute, in loquacious interface we have a method call, and when we had a nested element, we have a nested lambda expression.
<!-- HBM XML fragment -->
<property name="Example" lazy="false">
<column name="ColumnName" />
</property>
// mapping-by-code fragment
Property(x => x.Example, m =>
{
m.Lazy(false); // attribute equivalent
m.Column(c => c.Name("ColumnName")); // nested element equivalent
});
The first and most important thing to note is that loquacious interfaces supports tree structures, contrary to fluent chains, which are linear in its nature. As Martin Fowler mentions, fluent chains are "designed to be readable and to flow". Loquacious interface flows less, giving an ability to define arbitrarily complex structures insted, without losing on readability.
The only loss I can see (apart from less code needed to implement the API) is that there's no ability to enforce how many times and in what order methods are called - in the chain we can control it with types returned from each chain element, in loquacious interface's lambdas we have no control over how the methods are called.
How is it build?
What delights me is that there's no rocket science in loquacious interfaces at all (opposed to fluent chains which are hard to be designed well - see Fowler's article or my thoughts on Fluent NHibernate's component mapping). As we've seen in mapping-by-code example above, we have two types of methods inside lambdas in loquacious API - taking either simple object or another lambda. Methods with simple object-typed parameter are to modify the current level of structure we're creating, methods with lambda-typed parameter start a new level.
Suppose we want to use loquacious API to create a simple object tree like this:
var building = new Building()
{
Address = "1 Example Street",
Floors = new[]
{
new Floor()
{
Rooms = new[]
{
new Room() { Area = 33.0 },
new Room() { Area = 44.0 }
}
},
new Floor()
{
Rooms = new[]
{
new Room() { Area = 20.0 },
new Room() { Area = 30.0 },
new Room() { Area = 40.0 },
}
},
},
Roof = new Roof() { Type = RoofType.GableRoof }
};
To start building our Building, we have to create its first level using a method having Action<T>-typed parameter (Action<T> is a generic delegate taking single T parameter with no return value). T generic type should allow setting up elements available at given level - Address, Floors and Roof in this case. Let's sketch the starting point method's signature and prepare the interface used within its parameter:
public Building Building(Action<IBuildingCreator> action) { }
public interface IBuildingCreator
{
void Address(string address);
void Floor(Action<IFloorCreator> action);
void Roof(Action<IRoofCreator> action);
}
Address represents a simple Building's property, so it just has a string parameter. Floor and Roof represents complex objects, so we have another Action<T> parameters there. Methods have no return values - no chaining, standalone calls only.
Let's now implement our starting point method:
public Building Building(Action<IBuildingCreator> action)
{
var creator = new BuildingCreator();
action(creator);
return creator.TheBuilding;
}
We're instantiating an IBuildingCreator implementation and passing it to the action provided by our API user as lambda expression. IBuildingCreator's implementation creates a Building instance and exposes it through TheBuilding property. Each IBuildingCreator's method called by the user is supposed to modify that instance. Let's see the implementation:
internal class BuildingCreator : IBuildingCreator
{
private readonly Building _building = new Building();
public void Address(string address)
{
_building.Address = address;
}
public void Floor(Action<IFloorCreator> action)
{
var creator = new FloorCreator();
action(creator);
_building.Floors.Add(creator.TheFloor);
}
public void Roof(Action<IRoofCreator> action)
{
var creator = new RoofCreator();
action(creator);
_building.Roof = creator.TheRoof;
}
public Building TheBuilding { get { return _building; } }
}
The Building instance is created on BuildingCreator instatiation and modified by its members. Address method just sets up Building's property. Floor method repeats already known pattern - it creates the FloorCreator and appends newly built Floor to Floors collection. Roof method uses the same pattern again to assign Building's Roof property.
Note that we don't distinguish whether we're adding the element to a collection (like Floors) or we're assigning a single value (like Roof) at API level - it should be known from the semantics. Also note that the BuildingCreator class is internal and TheBuilding property is not included in the IBuldingCreator interface, so it stays our private implementation detail and don't need to be a part of public API we're creating - and that's quite neat.
Here's how to use the API we've just designed:
var building = Building(b =>
{
b.Address("1 Example Street");
b.Floor(f =>
{
f.Room(r => r.Area(33.0));
f.Room(r => r.Area(44.0));
});
b.Floor(f =>
{
f.Room(r => r.Area(20.0));
f.Room(r => r.Area(30.0));
f.Room(r => r.Area(40.0));
});
b.Roof(r => r.Type(RoofType.GableRoof));
});
The source code for this example is available on GitHub.
By following that pattern we can build an arbitrarily complex structures - we're not limited by the API design and its implementation will stay very simple - no method will exceed 3 lines of code. We can add new properties and levels easily, without breaking the API. Moreover, we have strongly typed lambdas everywhere, so our API can expose only methods that are valid at given point (not so easy with complex fluent chains). What's more, if we have recurring object patterns in different parts of our structure, we can reuse the same IXyzCreator interfaces and its implementations without any cost at all (again, try to do it within fluent chains).
Well, I'm quite impressed how many advantages this simple idea brings for us. I'm going to stick to that topic for a while to show some usages and "real" implementations of loquacious interfaces. Hope you'll enjoy!
Can you please post the source code for the article? I have a pretty interesting usage scenario for this technique in my application, but I cannot figure out all the workings of the class. Any help will be highly appreciated. Thanks.
ReplyDeleteHere you are, I've published it on GitHub. Let me know about your results!
DeleteMy application prints a lot of letters. But all letters have the same structure, i.e. Date, Customer it is addressed to, Subject... you get the drift. I am now constructing different letters in this way and although it is a little more work, the process seems more "natural" now. Thanks a lot. :)
Delete