Fluent Interfaces And Readability
posted on 05/29/08 at 10:52:45 pm by Joel Ross
I've heard people throwing the term "Fluent Interface" around, and, until recently, I had no idea what it meant - and more importantly, why I should care.
But then I started using one. At first, I didn't know I was using a fluent interface until I saw an example of a different one, and thought, "That's what I'm using!" I didn't even fully understand it at that point, but it definitely made the code a lot easier to follow.
So what was I using? Ninject, of course! For those who don't know, Ninject is a dependency injection framework, and it uses fluent interfaces for it's binding syntax. Here's an example of how you can bind an implementation to an interface to be used for a particular type:
Bind<IMessageLogger>().To<ConsoleLogger>().For<ConsoleService>();
In a typical implementation, the same thing is accomplished through a series of separate statements where you have to take into account the return types so you can deal with another call later on. Method names are typically longer because they're meant to be used in a stand alone fashion. By doing it this way, your method names tend to be shorter, but looking at that one line, it's pretty easy to see what's going on: I want to use ConsoleLogger whenever ConsoleService requests IMessageLogger.
So how do you create a fluent interface? It's pretty simple, really. I'll use orders and order lines as a simple example (even though Scott Hanselman is tired of Northwind!) I want to be able to write code like this:
order.AddLine("CPU", 2).AddLine("1GbRam", 4).SetTax("49456");
To accomplish that, we'd have an Order class that looks something like this:
1: public class Order
2: {
3: List<OrderLine> OrderLines { get; set; }
4:
5: public Order AddLine(string lineId, int quantity)
6: {
7: this.OrderLines.Add(new OrderLine(lineId, quantity));
8: return this;
9: }
10:
11: public Order SetTax(string zipCode)
12: {
13: // Calculate Tax based on Zip Code
14: return this;
15: }
16: }
The key is the return type. In this case, the two methods return Orders, so returning "this" is what we want - this allows you to chain your method calls together. You can also change the return type. This allows you (as the interface creator) to have control over what the next action can be.
Right now, I'm having fun using fluent interfaces. Eventually, I'll start to feel comfortable enough to start incorporating this into my own libraries. For now, I think I need to understand where it makes sense to use this a bit more first.
Tags: Fluent Interface | Ninject
Categories: Development, C#
8 comments
++Alan
Thanks for the comment. I really, really like Ninject. I think part of it has to do with the easy access to it's creator on twitter, but still, it's so clean and easy to work with.
Joel
Good post!
Such as order.AddLine("CPU", 2).and.AddLine("1GbRam", 4).then.SetTax("49456").but.setDiscount(.1);
There is a subtle difference, but it adds a few more words to the line to make it more readable.
I may be wrong, but that's what I thought it was all about.
Thanks for the feedback. I appreciate the support. A lot of times I don't understand the terms I hear, so once I figure them out, the easiest way to solidify that learning for me is to write it out - teach others, in a way.
Thanks for the comments. I do see some value in what you're saying. By controlling the return types through methods like "then" you not only get better readability, you also can control return types so after then, your only options could be setting tax and saving, for example.
I think the original is just as readable withoutt the "and" methods. The problem I see with methods like "and" is that they really do nothing but "return this;", which to me is a waste. Maybe I'm missing something, but it doesn't seem worth the extra effort.
