Of the vast catalog of Design Patterns available at our disposal, often times I find it is the simpler, less prominent patterns which are used quite frequently, yet recieve much less recognition; a good example of which being the Method Chaining Pattern.
Method Chaining
The Method Chaining Pattern, as I have come to appreciate it over the years, represents a means of facilitating expressiveness and fluency when used articulately, and mere convenience in it’s less sophisticated use-cases.
Design Considerations
When considering Method Chaining, one should take heed not to simply use the pattern as merely syntactic sugar from which writing fewer lines of code can be achieved; but rather, Method Chaining should be used, perhaps more appropriately, as a means of implementing Fluent APIs which, in turn, allow for writing more concise expressions. By design, such expressions can be written, and thus read, in much the same way as natural language, though they need not be the same from a truly lexical perspective.
The resulting terseness afforded by Method Chaining, while convenient, is in most cases not in-of-itself a reason alone for leveraging the pattern.
Implementation
Method Chaining, when considered purely from an implementation perspective, is perhaps the simplest of all design patterns. It’s basic mandate simply prescribes returning a reference to the object on which a method is being called (in most languages, JavaScript in particular, the this
pointer).
Consider the following (intentionally contrived) example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | var Chain = function(val){ this.init(val); }; var fn = Chain.prototype; fn.init = function(val){ this.links = isNaN(val) ? 0 : val; }; fn.addLinks = function(val){ if (!isNaN(val)) { this.links += val; this.links = this.links < 0 ? 0 : this.links; } return this; }; fn.removeLinks = function(val){ if (!isNaN(val)) { this.links -= val; this.links = this.links < 0 ? 0 : this.links; } return this; }; var chain = new Chain(); console.log(chain.addLinks(10).removeLinks(5).links); // 5 |
As can be seen, implementing Method Chaining requires nothing more than simply having methods return a reference to this
.
API Simplicity
Method Chaining is typically used when breaking from traditional Command Query Seperation (CQS) principles. The most common example being the merging of both getters (Queries) and setters (Commands). I especially like this technique, as, aside from being very easy to implement, it allows for an API to be used in a more contextual manner from the developers perspective as oppossed to that specified by the API designer’s preconceptions of how the API will be used. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | var Messenger = function(msg){ this.val = msg || ''; }; var fn = Messenger.prototype; fn.message = function(msg){ if (msg){ this.val += msg + ' '; return this; } return this.val; }; console.log( new Messenger() .message('Hello, World') .message() ); // Hello World |
As can be seen, the message
method serves as both a getter
and setter
, allowing users of the API to determine how the method should be invoked based on context, as well as affording developers the convenience of needing only to remember a single method name. This technique is used quite heavily in many JavaScript libraries and has undoubtedly contributed to their success.
We could further expand on this concept by determining a method’s invocation context based on the arguments provided, or the types of specific arguments, thus, in turn, merging various similar methods based on a particular context.
An important design recommendation to consider is that if you are writing an API which violates CQS (which is quite fine IMHO), as always, API consistency is important, thus all getters
and setters
should be implemented in the same manner.
Fluency
As was mentioned, in most cases, Method Chaining is leveraged to facilitate APIs which are intended to be used fluently (e.g. an Internal DSL). Such implementations typically provide methods which, by themselves, may have little meaning; however, when combined, allow for writing expressions which are self-descibing and make logical sense to users of the API.
For example, consider the way one might describe a Calendrical Event:
Vacation, begins June 21st, ends July 5th, recurs Yearly.
We can easily implement a Fluent API such that the above grammar can be emulated in code as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var Event = function(name){ ... }; var fn = Event.prototype; fn.begins = function(time){ ... return this; }; fn.ends = function(time){ ... return this; }; fn.recurs = function(when){ ... return this; }; var vacation = new Event('Vacation'); vacation.begins('June 21st'').ends('July 5th').recurs('Yearly'); |
The same methods can also be chained in different combinations, yet yield the same value:
1 2 3 4 | var vacation = new Event('Vacation'); vacation.recurs('Yearly').ends('July 5th').begins('June 21st''); |
Given the above example, we could further improve on the fluency of the implementation by adding intermediate methods which can, by themselves, simply serve to aid in readability, or, provide an alternate modifier for chaining:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | var Event = function(name){ ... }; var fn = Event.prototype; fn.begins = function(time){ ... return this; }; fn.ends = function(time){ ... return this; }; fn.recurs = function(when){ ... return this; }; fn.on = function(when){ ... return this; }; fn.at = function(when){ ... return this; }; fn.every = function(when){ ... return this; }; var vacation = new Event('Vacation'); vacation.begins.on('June 21st').at('5pm').ends.on('July 5th').at('11pm').recurs.every('Year'); |
When implementing Fluent APIs, we can design such that different logical chaining combinations can yield the same result, thus affording users of the API the convenience of determining the most appropriate expressions based on context or personal preference, even grammatically so. Illogical chaining combinations can be handled by either throwing an exception, or they can simply be ignored based on the context of a preceding invocation – though, of course, one should aim to avoid designs which allow for illogical chaining.
The Ubiquitous Example – jQuery
While Method Chaining and Fluent APIs, as with most design patterns, are language agnostic, in the JavaScript world perhaps the most well known implementation is the jQuery API; for example:
1 2 3 | $('#test').css('color','#333').height(200); |
In addition to jQuery, there are numerous additional JavaScript Method Chaining and Fluent APIs of note, Jasmine in particular has a very expressive API which aligns excellently with it’s design goals. The various libraries which implement the Promises/A spec also provide very clear and concise Fluent APIs.
Concluding Thoughts
Over the years I have leveraged Method Chaining to facilitate the design of Fluent APIs for various use-cases. The two patterns, when combined, can be especially useful when designing Internal DSLs; either third-party libraries, or APIs specific to a particular business domain.