Cascade Notation and Fluent Interface in Dart
Tags: dart, beginners, codenewbie, programming
Published: 2023-09-17 03:42:55

## Question 1

[Dart documentation](https://dart.dev/language/operators#cascade-notation) says:

> Cascades (.., ?..) allow you to make a sequence of operations on the same object. In addition to accessing instance members, you can also call instance methods on that same object. This often saves you the step of creating a temporary variable and allows you to write more fluid code.

Consider the following code:

```dart
class Car {
  String? color;
  int speed = 0;

  void accelerate() {
    speed += 10;
  }

  void brake() {
    speed -= 10;
  }

  void repaint(String newColor) {
    color = newColor;
  }

  @override
  String toString() {
    return "Car(color: $color, speed: $speed)";
  }
}

void main() {
  Car myCar = Car()
    ..color = 'red'
    ..speed = 100
    ..accelerate()
    ..repaint('blue');

  print(myCar); // Output: Car(color: blue, speed: 110)
}
```

The constructor, `Car()`, returns a `Car` object. The code that follows the cascade notation operates on this object, ignoring any values that might be returned. Here, `myCar` is created and then immediately modified using several methods and property setters, all thanks to the cascade notation (..). This allows for a more compact and chained setting of properties and calling of methods on the `myCar` object.

The previous example is equivalent to this code:

```dart
class Car {
  String? color;
  int speed = 0;

  void accelerate() {
    speed += 10;
  }

  void brake() {
    speed -= 10;
  }

  void repaint(String newColor) {
    color = newColor;
  }

  @override
  String toString() {
    return "Car(color: $color, speed: $speed)";
  }
}

void main() {
  Car myCar = Car();
  myCar.color = 'red';
  myCar.speed = 100;
  myCar.accelerate();
  myCar.repaint('blue');

  print(myCar); // Output: Car(color: blue, speed: 110)
}
```

Here are the differences:

```diff
-  Car myCar = Car()
-    ..color = 'red'
-    ..speed = 100
-    ..accelerate()
-    ..repaint('blue');
+  Car myCar = Car();
+  myCar.color = 'red';
+  myCar.speed = 100;
+  myCar.accelerate();
+  myCar.repaint('blue');
```

This reminds me of [Fluent Interface](https://github.com/ranggakd/clean-code-php/tree/pr_only#avoid-fluent-interfaces) after fork clean-code-php repo a while ago.

> A [Fluent interface](https://en.wikipedia.org/wiki/Fluent_interface) is an object-oriented API that aims to improve the readability of the source code by using [Method chaining](https://en.wikipedia.org/wiki/Method_chaining).

So I asked ChatGPT for direction.

> Is the concept of fluent interface the same as cascade notation in Dart?

## ChatGPT Reply 1

> Both the concept of a fluent interface and Dart's cascade notation (`..`) aim to make code more readable and convenient by allowing method calls or operations to be chained together. **However, they are not exactly the same thing.**

### Fluent Interface

In a fluent interface, each method **returns an instance of the object, allowing you to chain method calls together.** A fluent API is designed specifically to return `this` or a new instance of the same class, making it possible to chain methods.

Here's an example using a hypothetical `Car` class:

```dart
class Car {
  String? color;
  int speed = 0;

  Car accelerate() {
    speed += 10;
    return this;
  }

  Car brake() {
    speed -= 10;
    return this;
  }

  Car repaint(String newColor) {
    color = newColor;
    return this;
  }

  @override
  String toString() {
    return 'Car(color: $color, speed: $speed)';
  }
}

void main() {
  Car myCar = Car()
      .accelerate()
      .brake()
      .repaint('blue');
  print(myCar);  // Output: Car(color: blue, speed: 0)
}
```

### Cascade Notation

Dart's cascade notation allows you to **perform multiple operations on a single object without the need for the methods to return `this`.** When you use the cascade notation, Dart knows to use the same object for each subsequent operation.

```dart
void main() {
  Car myCar = Car()
    ..accelerate()
    ..brake()
    ..repaint('blue');

  print(myCar);  // Output: Car(color: blue, speed: 0)
}
```

### Summary

- A fluent interface is a design pattern where the methods are designed to be chained together.
- Cascade notation is a language feature in Dart that allows you to chain multiple operations on an object, whether or not the methods are designed to be chained together.

Both achieve a similar goal but do it in slightly different ways. Fluent interfaces are more common in languages that don't have an equivalent to Dart's cascade notation.

## Intermezzo

That's a good answer actually. So I moved on and read the next line from the [Dart documentation](https://dart.dev/language/operators#cascade-notation).

> If the object that the cascade operates on can be null, then use a _null-shorting_ cascade (?..) for the first operation. Starting with ?.. guarantees that none of the cascade operations are attempted on that null object.

I'm using my own code to understand this (probably a poor example but I get what the docs trying to say)

```dart
void main() {
  var myCar;
  myCar
    ?..color = 'red'
    ..speed = 100
    ..accelerate()
    ..repaint('blue');

  print(myCar); // Output: null
}
```

The previous code is equivalent to the following:

```dart
void main() {
  var myCar;
  myCar?.color = 'red';
  myCar?.speed = 100;
  myCar?.accelerate();
  myCar?.repaint('blue');

  print(myCar); // Output: null
}
```

Here are the differences:

```diff
  var myCar;
-  myCar
-    ?..color = 'red'
-    ..speed = 100
-    ..accelerate()
-    ..repaint('blue');
+  myCar?.color = 'red';
+  myCar?.speed = 100;
+  myCar?.accelerate();
+  myCar?.repaint('blue');
```

## Question 2

The [Dart documentation](https://dart.dev/language/operators#cascade-notation) says:

> Be careful to construct your cascade on a function that returns an actual object. For example, the following code fails:

```dart
var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: method 'write' isn't defined for 'void'.
```

I don't really get it since my all methods are void indeed. So I asked ChatGPT again.

> What do the docs mean by the void? Meanwhile, the previous snippet of code worked just fine with the void function.

# ChatGPT Reply 2

> The documentation is warning you that you cannot use cascade notation on a function or method that returns `void`. This is because the cascade notation works by treating the value returned by each cascaded operation as the object to perform the next operation on. In the case of methods that return `void`, there is no value to cascade onto.

However, you'll notice that **your Dart example works** because **you're not actually cascading on the return value of the `void` methods (`accelerate`, `brake`, `repaint`)**. You're cascading on **the original `Car` object (`myCar`)** which the **cascade notation keeps "in focus" throughout the chained operations.** In this context, `void` methods are permitted because they're not breaking the cascade; the **cascade remains rooted in `myCar`**.

For example, this would be problematic:

```dart
void main() {
  // This is an attempt to cascade on the result of accelerate(),
  // but that's void, so this will result in a syntax error.
  var myCar = Car().accelerate()..brake();  // Error
}
```

But in your working example:

```dart
Car myCar = Car()
  ..color = 'red'
  ..speed = 100
  ..accelerate()
  ..repaint('blue');
```

You are setting **the initial object for the cascade to `Car()`**, and **the cascade continues to operate on that object, not on the return values of the `void` methods.**

So, in summary, you can include `void` methods within a cascade, as long as you're still rooted in an object that isn't `void`.