There’s been some resurgent chatter about how to put together a fluent interface, whether they qualify as DSLs, etc. I think they do qualify as a DSL just as I think that a Domain Model qualifies as a kind of DSL. Certainly there’s a spectrum of “DSL-ness” for internal DSLs where fluent interfaces and domain model fall at one end and meta-programming powered custom grammars fit a bit further up. Even so, I don’t think the distinction matters all that much as all of these techniques help the goal of clarifying intent.
Controversies aside, I’d like to share one technique for imposing grammar in a fluent interface. By grammar I mean that sometimes you’ll find it’s ideal to employ a fluent interface in a situation where the call to configuration methods need to be in some kind of set order. If you buy that, I’ll make a distinction between Ordered and Unordered Fluent Interfaces. Most examples you see of fluent interfaces in the wild are using a Factory Method on a class that hosts a variety of instance methods that configure or call behavior internally and escape by returning the instance itself. I won’t drill into this too deeply as there are plenty of good examples of how one can make this happen, but here’s a quick snapshot of what these things tend to look like:
Configuration c1 = Configuration.AddFile(“my-file.txt”)
.Logging(true)
.Tracing(false)
.ExceptionShield(someShield);
Configuration c2 = Configuration.ScanAssembly(someAssembly)
.Tracing(true);
SomeController controller1 = new SomeController(c1);
SomeController controller2 = new SomeController(c1);
Notice (or pretend to anyway) in this example it doesn’t matter in which order we call the various configuration methods and that we can get the instance of the configured object we need at any time. That is, once we call the static methods AddFile or ScanAssembly we can call Tracing (which returns this) in any order. Each of the methods on the Configuration object returns a reference to the Configuration object itself and at any point we can drop out of deep dotting in our fluent interface and return an object for use.
What this style of fluent interface presuposses is that the initial, static, Factory Method will return a valid Configuration object. Great. Very simple and workable in 99% of cases where you’re using a fluent interface to configure an object or process a series of commands on an instance, etc. But what if you want to build up an order-dependent structure? A good thought experiment for this kind of problem is the sentence.
Using Interfaces to Control Flow
You can leverage interfaces to control the order of operations on our fluent interfaces. Let’s introduce a contrived example where we have a need to model a sentence grammar with a fluent interfaces. The main requirements are that a Sentence object must produce grammatically valid English and that we’re limiting the potential structure of a sentence to the following rules:
- Every sentence MUST start with a person’s name, a pronoun. You, me, and they are not supported in this version.
- A verb must follow the person’s name. We need three verbs for our application: Run, Throw, Eat. (What a weird application this is: again, contrived.)
- A simile can be used after a verb: e.G. John eats like a pig.
- Sentence text must end with a period or question mark.
- A sentence can only end after a verb or adverb has been used.
In this example order matters and if so we need to control the flow, otherwise we could end up with invalid language. We can knock out requirement #1 pretty easily by using a factory method on Sentence:
public class Sentence
{
private StringBuilder _text = new StringBuilder();
// force creation of instance through Sentence.Subject()
private Sentence() {}
public static Sentence Subject(string someone)
{
Sentence result = new Sentence();
result.AppendText(someone);
return result;
}
public string Text
{
get { return this._text.ToString(); }
}
private void AppendText(string text, bool space)
{
this._text.Append(text);
if (space) this._text.Append(” “);
}
}
I’ve got a couple of helper methods for appending to the sentence as we dot along our API. Other than that pretty straight forward. Now let’s tackle the second requirement which states that a verb must follow our sentence subject. I’ll introduce an interface and make a few tweaks to the Sentence object:
public interface IAfterSubject
{
IAfterVerb Runs { get; }
IAfterVerb Throws { get; }
IAfterVerb Eats { get; }
}
public class Sentence : IAfterSubject
{
private StringBuilder _text = new StringBuilder();
public static IAfterSubject Subject(string someone)
{
Sentence s = new Sentence();
s.AppendText(someone);
return s;
}
public IAfterVerb Runs
{
get
{
this.AppendText(“runs”);
return this;
}
}
public IAfterVerb Throws
{
get
{
this.AppendText(“throws”);
return this;
}
}
public IAfterVerb Eats
{
get
{
this.AppendText(“eats”);
return this;
}
}
}
So I’ve made Sentence implement IAfterSubject. This means I can simply return “this” from the Runs, Throws, and Eats methods. Bet you can guess what comes next:
public interface IAfterVerb : IEndOfSentence
{
IAfterSimilie LikeA(string noun);
IAfterAdverb Adverb(string adverb);
}
Of course I implement this interface on my Sentence class. Note that IAfterVerb also implements IEndOfSentence. This is because I can do more than one category of thing after I reach the verb part of my sentence. I can choose to end the sentence right there “Bob eats” or I can employ simile “Bob eats like a pig” (no offense, Bob).
If I choose to end the sentence, per my requirements, I’ll need to finish with a period or question mark, we’ll implement like so:
public interface IEndOfSentence
{
Sentence Period();
Sentence QuestionMark();
}
The Period and QuestionMark methods both return the sentence. Their implementation is straightforward. They add append “.” or “?” to the text of my sentence and return a full sentence object that I can then do with as I need including calling the Text property which gives me the, well, text or using the Sentence as part of a larger Composite, say we have a Paragraph object somewhere.
A few more of these interfaces and we’re able to compose type-safe statements that look like this:
Sentence s1 = Sentence.Subject(“Bob”)
.Eats
.LikeA(“pig”)
.Period();
Sentence s2 = Sentence.Subject(“Jane”)
.Runs
.Adverb(“crazily”)
.QuestionMark();
Sentence s3 = Sentence.Subject(“John”)
.Throws
.LikeA(“girl”)
.Period();
Alternative Uses and Implementations
A powerful technique there, but definitely not the only way to skin a cat. If you need to provide flow in an additive sense (that is one statement on my interface will add additional methods) you can try a return a decorator instead of simply returning a role interfaced “this.” I am sure there many ways to accomplish controlling the flow. Generics spring to mind as an interesting possibility in terms of branching your choices or controlling statement input. Still, keeping it simple, I like to keep the logic in my Sentence class and can control the API very tightly with multiple, role interfaces. The possibilities are varied and many.
It’s important to remember that this technique doesn’t necessarily have to follow through the whole fluent interface. My sentence example shows a rigid extreme, but you could easily host methods on the class hosting the fluent interface that simply return a straight-up “this” instance but occasionally provide methods that lead to “flow fragments” where you enforce a particular order. The first example that springs to mind is a Query Object where you want to create some syntax like Between(”DateField”).InTwoWeeks then pop back into regular, unstructured flow. Between returns an IExpressionsAfterBetween which itself implements a bunch of IExpression interfaces (IInTwoWeeks, INextQuarter, IDateRange) where each “expression method” then returns a QueryObject (the “this”) or, even better, an IAfterExpression interface with an And method, an Or method and a Query property that returns the QueryObject itself.
To deal with all this interface madness it’s pretty easy to stuff them into a sub-namespace as you won’t need a using to work off the class that’s hosting the fluent interface. I sometimes use this technique to keep auto-complete noise to a minimum.