Introduction
A fluent Interface or Cascade Design Pattern is a design pattern that allows you to chain method calls together in a readable and fluent way. The idea behind the pattern is to create an object that has methods that return the object itself so that you can chain the calls together in a readable way.
Cascade Pattern prior to Java 8
Steps:
Define a class that represents the object you want to cascade the method calls on. This class should have methods that return the object itself so that you can chain the calls together.
In the class, define each method that you want to be able to cascade. Each of these methods should return the object itself.
For example, take this Mailer
class. We need to set multiple properties (or perform operations) before sending mail. In this case, we can use the cascade pattern to call required methods and perform send operation in a readable/fluent way.
class Mailer {
public Mailer from(String from) {
System.out.println("from " + from);
return this;
}
public Mailer to(String to) {
System.out.println("to " + to);
return this;
}
public Mailer subject(String subject) {
System.out.println("subject " + subject);
return this;
}
public Mailer body(String body) {
System.out.println("body " + body);
return this;
}
public void send() {
System.out.println("sending ...");
}
}
The client can use the Mailer
class as below:
public static void main(String[] args) {
new Mailer()
.from("mailer@gmail.com") // return instance of Mailer
.to("sandeep@gmail.com") // return instance of Mailer
.subject("Message from mailer") // return instance of Mailer
.body("...details of message...") // return instance of Mailer
.send(); // perform send operation
}
Output:
From: mailer@gmail.com
To: sandeep@gmail.com
Subject: Message from mailer
Body: ...details of message...
Sending Mail ...
Cascade Pattern in Java 8 (Using Lambda):
In the above example, it's the client's responsibility to instantiate the Mailer
object and then use it. Instead, the Mailer
class itself can create an object and then use it to send the email.
We can provide Consumer
to the send
function. This consumer would be responsible for triggering other required methods.
class LambdaMailer {
// Make constructor private so that only way to instantiate is through send method
private LambdaMailer(){}
public LambdaMailer from(String from) {
System.out.println("From: " + from);
return this;
}
public LambdaMailer to(String to) {
System.out.println("To: " + to);
return this;
}
public LambdaMailer subject(String subject) {
System.out.println("Subject: " + subject);
return this;
}
public LambdaMailer body(String body) {
System.out.println("Body: " + body);
return this;
}
// Make send method public and static, so class can be invoked without creating instance.
public static void send(Consumer<LambdaMailer> mailerConsumer) {
// Now, instantiation is responsibility of Mailer class
LambdaMailer instance = new LambdaMailer();
// Trigger methods required before sending the mail
mailerConsumer.accept(instance);
// Send mail
System.out.println("Sending Email...");
}
}
The client can use the LambdaMailer
class as below:
public static void main(String[] args) {
// Pass consumer to send method
LambdaMailer.send( mailer -> mailer
.from("lambda-mailer@linkedin.com")
.to("sandeep@linkedin.com")
.subject("Message from lambda mailer")
.body("... details of lambda message ..."));
}
Difference between Builder and Cascade design pattern
Builder Pattern | Cascade Pattern |
Used to construct an object | Used to perform multiple operations on an existing object |
Used to create an object by providing a set of parameters and then constructing the object based on those parameters | Used to perform multiple operations on an existing object without the need for explicit calls to each of the operations |