For example, in Dart, the cascade:
is equivalent to the individual calls:
Method cascading is much less common than method chaining – it is found only in a handful of object-oriented languages, while chaining is very common. A form of cascading can be implemented using chaining, but this restricts the interface; see comparison with method chaining, below.
This is particularly useful if the object is the value of a lengthy expression, as it eliminates the need to either list the expression repeatedly or use a temporary variable. For example, instead of either listing an expression repeatedly:
or using a temporary variable:
b = a.b(); b.c(); b.d();
cascading allows the expression to be written once and used repeatedly:
Comparison with method chaining
Given a method call
a.b(), after executing the call, method cascading evaluates this expression to the left object
a (with its new value, if mutated), while method chaining evaluates this expression to the right object.
The following chain (in C++):
is equivalent to the simple form:
b = a.b(); b.c();
The following cascade (in Dart):
is equivalent to the simple form:
Cascading can be implemented in terms of chaining by having the methods return the target object (receiver,
self). However, this requires that the method be implemented this way already – or the original object be wrapped in another object that does this – and that the method not return some other, potentially useful value (or nothing if that would be more appropriate, as in setters). In fluent interfaces this often means that setters return this instead of nothing.
Method chains and cascades were both introduced in Smalltalk; most subsequent object-oriented languages have implemented chains, but few have implemented cascades. In Smalltalk the semicolon operator can be used to send different messages to the same object:
self listPane parent color: Color black; height: 17; width: 11
Compare with separate statements, terminated with a period, also using a variable for abbreviation:
|parent| parent := self listPane parent. parent color: Color black. parent height: 17. parent width: 11.
One subtlety is that the value of a method call ("message") in a cascade is still the ordinary value of the message, not the receiver. This is a problem when you do want the value of the receiver, for example when building up a complex value. This can be worked around by using the special
yourself method that simply returns the receiver:
For example, the "add an object to a collection" method (
Collection>>add: anObject) returns the object that was added, not the collection. Thus to use this in a cascade in an assignment statement, the cascade must end with
yourself, otherwise the value will just be the last element added, not the collection itself:
all := OrderedCollection new add: 5; add: 7; yourself.
With ExpressionThatReturnsAnObject .SomeFunction(42) .Property = value End With
With..End With blocks in Visual Basic can be nested:
With ExpressionThatReturnsAnObject .SomeFunction(42) .Property = value With .SubObject .SubProperty = otherValue .AnotherMethod(42) End With End With
Among newer languages, Dart implements cascades, using a double-dot
.. "cascaded method invocation operation". Unlike Smalltalk, in Dart the value of a cascaded method invocation is the receiver (base object), not the value of the (uncascaded) method invocation, and thus there is no need for
yourself. Dart uses properties, and thus rather than using method syntax for getters and setters (
foo.getBar(); foo.setBar(b);), it uses field value/assignment syntax (
foo.bar; foo.bar = b;), and cascades work with assignments:
a..string = 'Hello world!' ..done = true;
is equivalent to:
a.string = 'Hello world!'; a.done = true;