As an author of a class you're often concerned with state and behavior. As a user of a class, you're often concerned with getting things done.
Supposed you want to provide a class that helps send out emails. You are concerned about getting various information for the mail. You want to know from whom it is, the list of recipients, the subject, the body (and more—like priority, urgency, etc.).
As a user of the class, you want to quickly send out the email with as much ease as you can get.
Let's start with the familiar Java like API and refactor that into a fluent API with context. Ready for the short ride?
public class Mailer
{
// In each function I println in this example to illustrate the method call
// You can substitute the actual implementation for these, my focus here is on the API
public void to(String[] toAddress)
{
System.out.print("to ");
for(String to : toAddress) { System.out.print(to + ", "); }
System.out.println();
}
public void from(String fromAddress) { System.out.println("from " + fromAddress); }
public void subject(String theSubject) { System.out.println("subject " + theSubject); }
public void body(String message) { System.out.println("body " + message); }
public void send() { System.out.println("send"); }
}
Here is a way to use the above API.
Mailer mailer = new Mailer();
mailer.from("johndoe@example.com");
mailer.to(new String[]{"janedoe@example.com", "bobdoe@example.com"});
mailer.subject("refactor to fluency and context");
mailer.body("Hello, ...");
mailer.send();
The output from the above code is shown below:
from johndoe@example.com
to janedoe@example.com, bobdoe@example.com,
subject refactor to fluency and context
body Hello, ...
send
There is too much noise in the above code. You are creating a Mailer object, and it caries around the information until (and even after) you call the send()method. Can you reuse this instance of Mailer to send out another email? That is not clear.
Let's solve one problem at a time. Let's remove some of the clutter in the above code by rewriting it in Groovy.
class Mailer
{
void to(String[] toAddress) { println "to ${toAddress.join(', ')}" }
void from(String fromAddress) { println "from $fromAddress" }
void subject(String theSubject) { println "subject $theSubject" }
void body(String message) { println "body $message" }
void send() { println "send" }
}
In the above version, I eliminated public as that is default in Groovy. (Also, the printf in to() method is a lot simpler to implement).
The Groovy code below shows how to use the above code—again, call to the to() method is a lot simpler. However, the code is only a notch better than the Java version.
mailer = new Mailer()
mailer.from 'johndoe@example.com'
mailer.to 'janedoe@example.com', 'bobdoe@example.com'
mailer.subject 'refactor to fluency and context'
mailer.body 'Hello, ...'
mailer.send()
The output from the above code is shown below:
from johndoe@example.com
to janedoe@example.com, bobdoe@example.com
subject refactor to fluency and context
body Hello, ...
send
The send() method has to be called last. Also, it still does not tell us if I can reuse the Mailer object or not. We will fix that in the next version below.
class Mailer
{
void to(String[] toAddress) { println "to ${toAddress.join(', ')}" }
void from(String fromAddress) { println "from $fromAddress" }
void subject(String theSubject) { println "subject $theSubject" }
void body(String message) { println "body $message" }
static send(closure)
{
def mailer = new Mailer()
closure(mailer)
println "send"
}
}
In the above version, I made the send() method a static method of Mailer. It takes a closure as parameter, creates a Mailer instance and sends it to the closure. The closure will work on the Mailer instance provided to it as shown below. Once the control returns back to the send() method it can take care of finishing up the job of actually sending the mail.
Mailer.send { mailer ->
mailer.from 'johndoe@example.com'
mailer.to 'janedoe@example.com', 'bobdoe@example.com'
mailer.subject 'refactor to fluency and context'
mailer.body 'Hello, ...'
}
Once you're done with the send, the Mailer instance is gone. The output from the above code is shown below:
from johndoe@example.com
to janedoe@example.com, bobdoe@example.com
subject refactor to fluency and context
body Hello, ...
send
There is still some noise and lack of context. We will fix that in the next final version below:
class Mailer
{
void to(String[] toAddress) { println "to ${toAddress.join(', ')}" }
void from(String fromAddress) { println "from $fromAddress" }
void subject(String theSubject) { println "subject $theSubject" }
void body(String message) { println "body $message" }
static send(closure)
{
def mailer = new Mailer()
mailer.with closure
println "send"
}
}
The send() method is creating an instance of Mailer and is asking the closure to execute in the context of that instance (the with() method sets the context or delegate object of the closure to the target object, in this case the Mailer instance. As a result, the closure will route calls to untargeted methods to the context object). The code to use the Mailer now reduces to this:
Mailer.send {
from 'johndoe@example.com'
to 'janedoe@example.com', 'bobdoe@example.com'
subject 'refactor to fluency and context'
body 'Hello, ...'
}
The output from the above code is below (same output as above, of course):
from johndoe@example.com
to janedoe@example.com, bobdoe@example.com
subject refactor to fluency and context
body Hello, ...
send
So, we started with
Mailer mailer = new Mailer();
mailer.from("johndoe@example.com");
mailer.to(new String[]{"janedoe@example.com", "bobdoe@example.com"});
mailer.subject("refactor to fluency and context");
mailer.body("Hello, ...");
mailer.send();
and ended with a much lighter, fluent syntax below:
Mailer.send {
from 'johndoe@example.com'
to 'janedoe@example.com', 'bobdoe@example.com'
subject 'refactor to fluency and context'
body 'Hello, ...'
}
That was hardly any effort, right? Like it?