Step builder pattern
Introduction
I've recently decided to use the Amazon SES API to send emails to my Microservices Weekly subscribers. What prompted me to use Amazon SES API is its price. It's cheap. However, the client Java API provided by Amazon is not so simple to interact with, so I decided to create a small wrapper around their API.
To make a long story short, the main purpose here is to share my experience with a less well-known derivate of the Builder pattern - Step Builder pattern.
The Step Builder pattern is an object creation software design pattern. It's not so commonly mentioned in popular readings about design patterns.
The Step Builder pattern offers some neat benefits when you compare it to a traditional builder pattern. One of the main Step Builder pattern benefits is providing the client with the guidelines on how your API should be used. It can be seen as a mixture of a builder pattern and a state machine and in fact, this pattern is often referred to as a wizard for building objects.
Pros and cons
Pros
- User guidance for your API through the object creation process step by step.
- API User can call the builder's build() method once the object is in a consistent state.
- Reduced opportunity for the creation of inconsistent object instances.
- Sequencing initialization of mandatory fields.
- Fluent API.
- No need for providing validate() method for field validation.
Cons
- Low readability of code needed to implement the pattern itself.
- No eclipse plugin to help with code generation. (On the other hand, there are plenty of code generators for Builder pattern generator).
Code-walkthrough
Since the Step Builder pattern is a creational design pattern, let's focus on its purpose - the creation of objects.
Example of API usage is shown below:
Email email = Email.builder().from(EmailAddress.of("Microservices Weekly <mw@microservicesweekly.com>"))
.to(EmailAddress.of("svlada@gmail.com"))
.subject(Subject.of("Subject"))
.content(Content.of("Test email"))
.build();
Now, let's see how API is enforcing the initialization of an object in the pre-defined order.
The following image is the graphical representation of the state machine for constructing Email object with the Step Builder pattern (mandatory values are marked purple and optional values are yellow):
Rules of thumb for implementation:
- Add dependencies to your class. It's recommended to add a private modifier to class attributes.
- Define creational steps as inner interfaces in your base class.
- Each creational step should return the next step (interface) in the chain.
- The final step should be an interface called "Build" which will provide build() method.
- Define one inner static Builder class that implements all of the defined steps.
- Implement step interface methods.
Source code
Complete Example of Step by Step builder pattern:
public class Email {
private EmailAddress from;
private List<EmailAddress> to;
private List<EmailAddress> cc;
private List<EmailAddress> bcc;
private Subject subject;
private Content content;
public static FromStep builder() {
return new Builder();
}
public interface FromStep {
ToStep from(EmailAddress from);
}
public interface ToStep {
SubjectStep to(EmailAddress... from);
}
public interface SubjectStep {
ContentStep subject(Subject subject);
}
public interface ContentStep {
Build content(Content content);
}
public interface Build {
Email build();
Build cc(EmailAddress... cc);
Build bcc(EmailAddress... bcc);
}
public static class Builder implements FromStep, ToStep, SubjectStep, ContentStep, Build {
private EmailAddress from;
private List<EmailAddress> to;
private List<EmailAddress> cc;
private List<EmailAddress> bcc;
private Subject subject;
private Content content;
@Override
public Email build() {
return new Email(this);
}
@Override
public Build cc(EmailAddress... cc) {
Objects.requireNonNull(cc);
this.cc = new ArrayList<EmailAddress>(Arrays.asList(cc));
return this;
}
@Override
public Build bcc(EmailAddress... bcc) {
Objects.requireNonNull(bcc);
this.bcc = new ArrayList<EmailAddress>(Arrays.asList(bcc));
return this;
}
@Override
public Build content(Content content) {
Objects.requireNonNull(content);
this.content = content;
return this;
}
@Override
public ContentStep subject(Subject subject) {
Objects.requireNonNull(subject);
this.subject = subject;
return this;
}
@Override
public SubjectStep to(EmailAddress... to) {
Objects.requireNonNull(to);
this.to = new ArrayList<EmailAddress>(Arrays.asList(to));
return this;
}
@Override
public ToStep from(EmailAddress from) {
Objects.requireNonNull(from);
this.from = from;
return this;
}
}
private Email(Builder builder) {
this.from = builder.from;
this.to = builder.to;
this.cc = builder.cc;
this.bcc = builder.bcc;
this.subject = builder.subject;
this.content = builder.content;
}
public EmailAddress getFrom() {
return from;
}
public List<EmailAddress> getTo() {
return to;
}
public List<EmailAddress> getCc() {
return cc;
}
public List<EmailAddress> getBcc() {
return bcc;
}
public Subject getSubject() {
return subject;
}
public Content getContent() {
return content;
}
}
Eclipse plug-in
So far, I haven't found a plug-in for Eclipse that provides Step Builder code generation feature.
I've created a GitHub repository to create an Eclipse plug-in that will provide support for Step Builder pattern generation: https://github.com/svlada/alyx