Benefits and drawbacks of method chaining and a possibility to replace all void return parameters by the object itself
Drawbacks
- Principally it confuses the signature, if something returns a new instance I don’t expect it to also be a mutator method. For example if a vector has a scale method then if it has a return I would presume it returns a new vector scaled by the input, if it doesn't then I would expect it to scale internally.
- Plus of course you get problems if the class is extended, where partway through your chaining your object gets cast to a supertype. This occurs when a chaining method is declared in the parent class, but used on an instance of the child class.
Benefits
-
It allows mathematical equation style code to be written as full equations without the need for multiple intermediate objects (leading to unnecessary overhead), for example without method chaining the vector triple cross product (as a random example) would have to be written either as
MyVector3d tripleCrossProduct=(vector1.multiply(vector2)).multiply(vector3);
which has the disadvantage of creating an intermediate object which must be created and garbage collected, or
MyVector3d tripleCrossProduct=vector1; tripleCrossProduct.multiplyLocal(vec2); tripleCrossProduct.multiplyLocal(vec3);
which avoids the creation of intermediate objects but is deeply unclear, the variable name
tripleCrossProduct
is in fact a lie until line 3. However, if you have method chaining this can be written concisely in a normal mathematical way without creating unnecessary intermediate objects.MyVector3d tripleCrossProduct=vector1.multiplyLocal(vector2).multiplyLocal(vector3);
All of this assumes that vector1 is sacrificial and will never need to be used again
-
And of course the obvious benefit; brevity. Even if your operations aren't linked in the manor of my above example you can still avoid unnecessary references to the object
SomeObject someObject=new SomeObject(); someObject .someOperation() .someOtherOperation();
NB MyVector3d
is not being used as a real class of Java, but is assumed to perform the cross product when .multiply()
methods are called. .cross()
is not used so that the 'intention' is clearer to those not familiar with vector calculus
NB Amit's solution was the first answer to use multiline method chaining, I include it as part of the forth bullet point for completeness
Method chaining is a way to implement fluent interfaces, regardless of the programming language. Main benefit of it (readable code) tells you exactly when to use it. If there is no particular need for the readable code, better avoid using it, unless the API is naturally designed to return the context/object as a result of the method calls.
Step 1: Fluent Interface vs. Command-Query API
Fluent interface must be considered against command-query API. To understand it better, let me write a bullet-list definition of the command-query API below. In simple words, this is just a standard object-oriented coding approach:
- Method that modifies the data is called a
Command
. Command does not return a value. - Method that returns a value is called a
Query
. Query does not modify the data.
Following the command-query API will give you the benefits like:
- Looking at the object-oriented code you understand what's happening.
- Debugging of the code is easier because each call happens separately.
Step 2: Fluent Interface on top of Command-Query API
But the command-query API exists for some reason, and it indeed, reads better. Then how do we have the benefits of both fluent interface and command-query API?
Answer: fluent interface must be implemented on top of the command-query API (as opposed to replacing the command-query API by the fluent interface). Think of a fluent interface as a facade over the command-query API. And after all, it's called fluent "interface" - a readable or convenience interface over the standard (command-query) API.
Usually, after the command-query API is ready (written, probably unit-tested, polished to easily debug), you can write a fluent interface software layer on top of it. In other words, fluent interface accomplishes its functions by utilizing the command-query API. Then, use the fluent interface (with method chaining) wherever you want a convenience and readability. However, once you want to understand what's actually happening (e.g. when debugging an exception), you can always dig into the command-query API - good old object-oriented code.
The downside I have found of using method chaining is debugging the code when NullPointerException
or any other Exception
happens. Suppose you are having following code:
String test = "TestMethodChain";
test.substring(0,10).charAt(11); //This is just an example
Then you will get String index out of range: exception when executing above code. When you get into realtime situations and such things happen then you get at which line error has happened but not what part of chained method caused it. So it needs to be used judiciously where it known that data will always come or errors are handled properly.
It has its advantages too like you need not write multiple lines of code and using multiple variables.
Many frameworks/tools use this like Dozer and when I used to debug the code it I had to look through each part of chain to find what caused error.
Hope this helps.