The danger of Fluent interfaces
December 30th, 2005 by Ivo
Ironically, Martin's original example already demonstrates the problem:
customer.newOrder() .with(6, "TAL") .with(5, "HPK").skippable() .with(3, "LGV") .priorityRush();
newOrder could be implemented in two ways:
a) Create an order and return that new order
b) Add an order to this customer and return the customer
b is the fluid approach, a is more logical. In this example it's a.
This can lead to confusion; the 'with()' method employs method b. If you think that newOrder returns an Order, and hence 'with()' is a method of the Order class, look at 'skippable'. Martin states that order lines are skippable. So what's in front of skippable() is an OrderItem. That means that with() must have returned an OrderItem. If that is true, then with() is a method of OrderLine too!
So this means that in this case, probably both Order and OrderLine have a with() method. Apart from the fact that a method named 'with()' is dangerous as this can mean anything (which might be problematic for the non-fluent programmer), apparently in this case we have to add additional methods (both to Order and OrderLine) in order to be able to use this fluently.
priorityRush further demonstrates this. Where the second with() returned an OrderLine, the third with() now returns the order as priorityRush() is a method of Order.
While this *looks* more readable, it is actually confusing. Or perhaps priorityRush is also a method of OrderLine, that forwards the call to its order. That would only further clutter things up as now OrderLine and Order seem to have a similar API. OrderLine has a priorityRush() method that doesn't belong there, or Order has a skippable() method that doesn't belong there .
It becomes more clear if you write out the API that is needed to create this example:
class Customer public Order newOrder() class Order public OrderLine with(int quantity, String code) public Order priorityRush() class OrderLine public OrderLine with(int quantity, String code) public OrderLine skippable() public Order priorityRush()
To create such an API only to be able to write more english-like sentences in code is, in my opinion, bad practice.
Only use this when it makes sense. If you like, make an add() method return the added item, so you can write stuff like:
item = table.add(new Item()); item.setBorder("red");
But not for readability purposes.