Diving Deeper into the World of Fluent APIs: An Unusual Way of Constructing Class Modules
Properly implemented, fluent interfaces can supercharge the signal-to-noise ratio of your code.
You can use them to turn these nine lines of code...
With CreateControl(CtlName, acLabel)
.Caption = "My Label"
.Width = 2 * TwipsPerInch
.TextAlign = 2 'Center
.FontSize = 14
.FontName = "Segoe UI"
.FontBold = True
.FontItalic = True
End With
...into this one line of code...
fb.AddLabel("My Label").Width(2).Center.Size(14).Font("Segoe UI").Bold.Italic
In isolation, this may not seem like that big of a deal. But what if your calling code needed to create dozens of controls? Suddenly, the difference is not 9 lines to 1, but rather hundreds of lines versus a couple of dozen. No one in their right mind should be writing procedures with hundreds of lines of code. But–in certain situations–you can use fluent interfaces to reduce hundreds of lines of code down to just a few dozen, without sacrificing readability:
A procedure with a few dozen lines of code will fit on-screen at one time in the VBA IDE. By having all your code on-screen at once, it makes it much easier to read and debug your code.
And human-readable code is not merely a nice-to-have feature. It is critical to writing maintainable applications, since almost all code is "Write Once, Read Many."
What are Fluent Interfaces?
Programming legend Martin Fowler coined the term "fluent interfaces" in a blog post back in 2005.
According to Fowler's site, "a fluent interface is a way of building an API so that its use has the feel of an internal domain-specific language." Rather than spend a lot of time describing the concept, he jumped right into a BEFORE and AFTER example. The examples are written in Java–not VBA–but the concept is easily transferable.
Traditional Style
private void makeNormal(Customer customer) {
Order o1 = new Order();
customer.addOrder(o1);
OrderLine line1 = new OrderLine(6, Product.find("TAL"));
o1.addLine(line1);
OrderLine line2 = new OrderLine(5, Product.find("HPK"));
o1.addLine(line2);
OrderLine line3 = new OrderLine(3, Product.find("LGV"));
o1.addLine(line3);
line2.setSkippable(true);
o1.setRush(true);
}
Fluent Style
The most notable feature of the fluent style is the use of "method chaining." With method chaining, each method is a function that returns an instance of the original object. This allows subsequent method calls to be "chained" together, one after the other.
The result, as you can see, is a very compact and readable block of code.
More than just Method Chaining?
Three years after writing the original article, Fowler posted an update following the original which reads, in part:
I've also noticed a common misconception - many people seem to equate fluent interfaces with Method Chaining. Certainly chaining is a common technique to use with fluent interfaces, but true fluency is much more than that.
Unfortunately, for Mr. Fowler, the horse was already out of the barn at that point.
I appear to be one of those people who "seem to equate fluent interfaces with Method Chaining." Perhaps that's because I read other articles about the concept before discovering Fowler's seminal work. In any case, I plan to continue using the two terms somewhat interchangeably–if for no other reason than it will make it easier to search for articles related to the topic on my website.
Additional Characteristics of Fluent Interfaces
I highly recommend you read Fowler's entire article, but in case you're lazy like me, I'll quote some of his key points here. Please note that the points below are not listed in the order they appear in Fowler's article.
"Setters that return a value"
Building a fluent API like this leads to some unusual API habits. One of the most obvious ones are setters that return a value.
In VBA, this manifests itself as Property Let
statements being converted into Function
statements.
For example, the Bold
and Italic
methods from my sample at the top of this article are defined as follows in my oFormBuilderCtl class module:
Public Function Bold() As oFormBuilderCtl
this.Control.FontBold = True
this.Control.SizeToFit
Set Bold = Me
End Function
Public Function Italic() As oFormBuilderCtl
this.Control.FontItalic = True
this.Control.SizeToFit
Set Italic = Me
End Function
Fluent methods "don't make much sense on their own"
One of the problems of methods in a fluent interface is that they don't make much sense on their own.
Continuing to reference my example at the top of this article, I have a method named NewLine
in my clsFormBuilder class module. The method resets the current X position to zero, increments the Y position by the height of the current line, resets the default line height, etc. A better name for the method in a traditional class module might be SetInsertionPointToStartOfNextLine
.
On its own, NewLine
would be an atrocious name for a method. Within the context of a form-building class module, though, its purpose becomes quite clear.
Fluent interfaces often appear "in a declarative context"
So far we've mostly seen fluent APIs to create configurations of objects, often involving value objects. I'm not sure if this is a defining characteristic, although I suspect there is something about them appearing in a declarative context. The key test of fluency, for us, is the Domain Specific Language quality. The more the use of the API has that language like flow, the more fluent it is.
Perhaps I internalized the true meaning of "Fluent Interfaces" more than I realized when I wrote my first article on the topic:
In fact, the very first heading in the above article is "Declarative programming." I noted that, "One of the major advantages of declarative languages [e.g., SQL] is that they tend to be very expressive." Fluent interfaces share this same focus on expressiveness.
A Readable API that "Flows"
The API is primarily designed to be readable and to flow.
Notice how well the following code flows, especially when you consider what it would look like using a more traditional style:
The first line creates a header label with the caption "Current Employer" on its own line, then makes it bold, italic, and gives it a 12-point font. On the next line, it creates a textbox with an attached label with the caption "Employer:", bound to a field named "EmployerName", and extends the text box to fill the available space to the right side of the form.
Here's what the above code produces when used to generate part of an Access form:
"A nice fluent API requires a good bit of thought"
The price of this fluency is more effort, both in thinking and in the API construction itself. The simple API of constructor, setter, and addition methods is much easier to write. Coming up with a nice fluent API requires a good bit of thought.
Having written several Fluent APIs over the past few years, I can echo this statement. My single-minded pursuit of backwards compatibility tends to lead to buyer's remorse toward the end of a Fluent API project. You'll realize that some design decision you made at the very beginning is one you'd like to have back. But, like anything, as you gain experience building Fluent APIs, you make fewer mistakes. And those mistakes you do make, you learn how to recover from.
"Strengths and Weaknesses" of Fluent APIs
I haven't seen a lot of fluent interfaces out there yet, so I conclude that we don't know much about their strengths and weaknesses.
I've recently finished implementing my fourth Fluent API, so I have some thoughts on their strengths and weaknesses. I'll cover those in a followup article.
External references
Image by Gerhard Bögner from Pixabay