Fluent Interfaces Are Bad for Maintainability
Fluent interface, first coined as a term by Martin Fowler, is a very convenient way of communicating with objects in OOP. It makes their facades easier to use and understand. However, it ruins their internal design, making them more difficult to maintain. A few words were said about that by Marco Pivetta in his blog post Fluent Interfaces are Evil; now I will add my few cents.
Let’s take my own library jcabi-http, which I created a few years ago, when I thought that fluent interfaces were a good thing. Here is how you use the library to make an HTTP request and validate its output:
String html = new JdkRequest("https://www.google.com") .method("GET") .fetch() .as(RestResponse.class) .assertStatus(200) .body();
This convenient method chaining makes the code short and obvious, right? Yes, it does, on the surface. But the internal design of the library’s classes, including JdkRequest
, which is the one you see, is very far from being elegant. The biggest problem is that they are rather big and it’s
impossible to extend them without making them even bigger.
difficult
For example, right now JdkRequest
has the methods method()
, fetch()
, and a few others. What happens when new functionality is required? The only way to add to it would be to make the class bigger, by adding new methods, which is how we jeopardize its maintainability. Here, for example, we added multipartBody()
and here we added timeout().
I always feel scared when I get a new feature request in jcabi-http. I understand that it most probably means adding new methods to Request
, Response
, and other already bloated interfaces and classes.
I actually tried to do something in the library in order to solve this problem but it wasn’t easy. Look at this .as(RestResponse.class)
method call. What it does is decorate a Response
with RestResponse
, in order to make it method-richer. I just didn’t want to make Response
contain 50+ methods, like many other libraries do. Here is what it does (this is pseudo-code):
class Response { RestResponse as() { return new RestResponse(this); } // Seven methods } class RestResponse implements Response { private final Response origin; // Original seven methods from Response // Additional 14 methods }
As you see, instead of adding all possible methods to Response
I placed them in supplementary decorators RestResponse
, JsonResponse
, XmlResponse
, and others. It helps, but in order to write these decorators with the central object of type Response
we have to use that “ugly” method as()
, which depends heavily on Reflection and type casting.
Fluent interfaces mean large classes or some ugly workarounds.
In other words, fluent interfaces mean large classes or some ugly workarounds. I mentioned this problem earlier, when I wrote about Streams API and the interface Stream, which is perfectly fluent. There are 43 methods!
That is the biggest problem with fluent interfaces—they force objects to be huge.
Fluent interfaces are perfect for their users, since all methods are in one place and the amount of classes is very small. It is easy to use them, especially with code auto-completion in most IDEs. They also make client code more readable, since “fluent” constructs look similar to plain English (aka DSL).
That is all true! However, the damage they cause to object design is the price, which is too high.
What is the alternative?
I would recommend you use decorators and smart objects instead. Here is how I would design jcabi-http, if I could do it now:
String html = new BodyOfResponse( new ResponseAssertStatus( new RequestWithMethod( new JdkRequest("https://www.google.com"), "GET" ), 200 ) ).toString();
This is the same code as in the first snippet above, but it is much more object-oriented. The obvious problem with this code, of course, is that the IDE won’t be able to auto-complete almost anything. Also, we will have to remember many of the names of the classes. And the construct looks rather difficult to read for those who are used to fluent interfaces. In addition, it’s very far away from the DSL idea.
Fluent interfaces are good for users, but bad for developers. Small objects are good for developers, but difficult to use.
But here is the list of benefits. First, each object is small, very cohesive and they are all loosely coupled—which are obvious merits in OOP. Second, adding new functionality to the library is as easy as creating a new class; no need to touch existing classes. Third, unit testing is simplified, since classes are small. Fourth, all classes can be immutable, which is also an obvious merit in OOP.
Thus, there seems to be a conflict between usefulness and maintainability. Fluent interfaces are good for users, but bad for library developers. Small objects are good for developers, but difficult to understand and use.
It seems to be so, but only if you are used to large classes and procedural programming. To me, a large amount of small classes seems to be an advantage, not a drawback. Libraries that are clear, simple, and readable inside are much easier to use, even when I don’t know exactly which classes out there are the most suitable for me. Even without the code-auto-complete I can figure it out myself, because the code is clean.
Also, I very often find myself interested in extending existing functionality either inside my code base or via a pull request to the library. I am much more interested to do that if I know that the changes I introduce are isolated and easy to test.
Thus, no fluent interfaces anymore from me, only objects and decorators.
Published on Java Code Geeks with permission by Yegor Bugayenko, partner at our JCG program. See the original article here: Fluent Interfaces Are Bad for Maintainability Opinions expressed by Java Code Geeks contributors are their own. |
It is not as black and white as you make it sound. You blame fluent interfaces for your naive implementation of them. That is not an indictment of fluent interfaces, it is of your implementation of them. There is nothing stopping you from wrapping these “smart objects”, which are arguably not that “smart” and decorators with a fluent interface. I actually have always implemented my fluent interfaces in a similar manner. They are just wrappers around state machines of method calls that must be done in a specific order. Nothing about them dictates you put any of the code in… Read more »
Using a blog post about an attempted PHP implementation of fluent interface pattern as a indictment of the entire pattern kind of discredits everything you say after that. Predicated on the fact that “PHP is evil” is they reason that person does not like the pattern in that language.
The fluent programming paradigm is horrible and should be shunned. The code is nearly un-debuggable and a nightmare to maintain. I say this as a 30 year veteran software engineer.