Method Chaining and Method Cascading
In the last lesson, Servant, we talked about the Servant design pattern. We use it if we want to add additional functionality to several classes without interfering with their interfaces.
In today's article, we're gonna explain the importance of auxiliary variables in source code and the Method Chaining design pattern with the fluent interface that the implementation of this pattern provides. We'll also explain Method Cascading and in which cases it replaces Method Chaining.
Motivation
Creating auxiliary variables isn't always necessary for the program to run, but it's important to keep the source code readable for the programmer. It's a common practice to create unnecessary variables that basically slow the program down, but allow people to read the source code. Sometimes they can also be counterproductive and it's better to get rid of them. Let's show examples of when it's worth using the auxiliary variables and when it's not.
Useful auxiliary variables
Let's start with a code that adds 2 numbers entered to the console as
strings. It parses the numbers to the int
type, sums and prints
them. Let's create too many auxiliary variables first:
string input1 = Console.ReadLine(); string input2 = Console.ReadLine(); int a = int.Parse(input1); int b = int.Parse(input2); int c = a + b; Console.WriteLine(c);
The input1
and input2
variables are basically
unnecessary here, and we can simply get rid of them without decreasing the
readability of the source code:
int a = int.Parse(Console.ReadLine()); int b = int.Parse(Console.ReadLine()); int c = a + b; Console.WriteLine(c);
We nest the methods into another. The code is now optimal, we can read it simply from left to right. If we keep nesting the methods, we could end up with a one-line program completely without auxiliary variables:
Console.WriteLine(int.Parse(Console.ReadLine()) + int.Parse(Console.ReadLine()));
However, it's quite hard to read this code, and we certainly shouldn't write it this way. This isn't because there are too many methods on one line, but the methods are recursively nested, and we need to read them from the middle and remember the intermediate results.
Note: In case of performance problems, removing the auxiliary variables may sometimes pay off in certain parts of the application. However, we usually handle these situations no before they actually happen, according to the Premature optimization is the root of all evil principle.
Unnecessary auxiliary variables
Sometimes, auxiliary variables are doing more harm than good. These are the cases where we need to keep manipulating with the same object and keep referring to the variable with it or modifying its instance somehow. Let's show a code that takes the first 50 characters from a given string, removes white characters around the string and converts it to lowercase. As true rookies, we'd even create a separate variable for each intermediate result, which we won't do. However, we could think of using an auxiliary variable to store the current result:
string s = "Who is General Failure and why is he reading my hard disk?"; s = s.Substring(0, 50); s = s.Trim(s); s = s.ToLower();
Repeating of s =
doesn't improve the code readability in any way
and just takes up space. Any repetition in the source code is almost always a
design mistake (see the DRY
principle).
Other unnecessary variables
Variables can also mess up source codes when we set different properties of one object:
Button button = new Button(); button.Text = "Save"; button.Width = 100; button.Height = 50; button.Frame = 2; button.Color = Color.Blue; button.Icon = Icon.FloppyDisk; button.Selected = true; button.Shortcut = {"ctrl", "s"};
The code is unnecessarily long and confusing. You may have thought of putting
all the parameters into the constructor, but that wouldn't make it very clear,
because we'd forget which parameter is which. E.g. the value of 2
would be very confusing without the information that it indicates the frame next
to it and assuming there are so many other values.
How to get out of it?
Method chaining
Method Chaining, sometimes referred to as Fluent Interface,
is the technique of calling a method directly on the return value of
another method without using auxiliary variables. This technique is
only possible if the object on which we call the method supports this usage,
meaning it exposes a fluent interface. Method Chaining is usually implemented by
a method returning the same object instance on which it's been called (by
returning this
), or returning an object with the same interface.
It's often used for strings, setters and
collections. However, we can also use it on other objects
types.
You have probably noticed that we can call methods like
ToLower()
, Substring()
, Trim()
and
similar on a string instance, one after another, since they always return a
string and are called on a string. At this point, you've used method chaining.
Let's look at what the deterrent example above would look like if we called the
methods one after another:
string s = "Who is General Failure and why is he reading my hard disk?"; s = s.Substring(0, 50).Trim(s).ToLower();
The result is very impressive and still perfectly readable as we call the methods from left to right.
Collections
Method Chaining is often used for working with collections. It doesn't even have to return the same data type, just the supported interfaces. In C# .NET, using Method Chaining, we can select all adult users from a collection, sort them by age, those with the same age by name and select their names. Thanks to the fluent interface, we can do it all on a single line:
var result = users.Where(u => u.Age >= 18).OrderBy(u => u.Age).ThenBy(u => u.Name).Select(u => u.Name);
In the example above, we take an advantage of the fact that each method is
called on a data collection and its result is also a data collection. So we can
call another method directly on the return value of the previous one. The
resulting notation is very clear and looks similar to SQL
language queries. If you are confused with the arrow operators
(=>
), that's C# syntax for lambda functions, anonymous methods
by which we specify what we need from the given method.
Setters
And what about the example with instantiating a button and setting a large number of its parameters? We'll implement setters supporting the fluent interface to the class and set the parameters with them:
Button button = new Button(); button.setText("Save") .setWidth(100) .setHeight(50) .setFrame(2) .setColor(Color.Blue) .setIcon(Icon.FloppyDisk) .setSelected(true) .setShortcut({"ctrl", "s"});
The code is now much more readable and shorter. The methods in the
Button
class would look like as follows:
public Button setText(string text) { this.Text = text; return this; } public Button setWidth(int width) { this.Width = width; return this; } public Button setHeight(int height) { this.Height = height; return this; } public Button setFrame(int frame) { this.Frame = frame; return this; } // ...
The construction of the object, along with the setters, is often separated to an external object called Builder, which increases the code readability.
Method Cascading
Some programming languages have Method Chaining built directly in their
syntax, such as the Dart language. We then refer to this mechanism as
Method Cascading. It's typically implemented through the
..
(double dot) operator and allows us to access properties or call
methods on an object without having to specify it again and again. Maybe some of
you remember the with
construction from old Pascal that did the
similar thing.
var button = new Button() ..text("Save") ..width(100) ..height(50) ..frame(2) ..color(Color.Blue) ..icon(Icon.FloppyDisk) ..selected(true) ..shortcut(["ctrl", "s"]);
In the same way, we can also call methods, and therefore replace Method Chaining completely, if supported by our programming language. Most of them are unfortunately unable to do so, so most often we just use the fluent interface.