Builder in Java and Ruby
Recently I’ve been context-switching between 2 different projects – one in Ruby and the other in Java – and I got a chance to utilize Builder Pattern (to be precise, it’s a variant of the classic builder pattern) in Java, and get impressed by how much simpler the solution could possibly be in Ruby.
The Problem
The goal is to create immutable objects with a mixture of mandatory and optional attributes. To simplify, all attributes are primitive types or String.
Here I use a Person
class as example, with following attributes:
// required fields
String firstName;
String lastName;
// optional fields
int age;
String address;
String email;
In Java
Naive Approach: Overloading Constructor
A naive implementation is to create overloaded constructors:
public class Person {
// fields omitted
public Person(String firstName, String lastName, int age, String address, String email) {...}
public Person(String firstName, String lastName, int age, String address) {...}
public Person(String firstName, String lastName, int age) {...}
public Person(String firstName, String lastName) {...}
...
}
This can become very complicated, as number of fields increases, which results in a lot more possible combinations of required and optional attributes in arguments.
With PersonBuilder
It’s not the best fit for classic Builder Pattern, but a good use case for one of its variants. The key responsibility of the builder in this case is the attributes setter of the Person
class. The general signature of the set
method is:
public PersonBuilder setAttributeA(AttributeA attribute)
It also return the builder self
so that the set of methods can be chained. Full code example for Person
class:
package BuilderPattern.BeanStyleExample;
public class Person {
// required fields
private final String firstName;
private final String lastName;
// optional fields
private int age;
private String address;
private String email;
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
void setEmail(String email) {
this.email = email;
}
}
Class diagram:
A few notes:
- required fields (
firstName
andlastName
) are marked asfinal
and are set in constructor; - the access for constructor and setters are set as package private. In this case, they can only be called by
PersonBuilder
, which is in the same package, but not directly by the external client.
Code for PersonBuilder
:
package BuilderPattern.BeanStyleExample;
public class PersonBuilder {
private Person person;
public PersonBuilder(String firstName, String lastName) {
person = new Person(firstName, lastName);
}
public PersonBuilder setAge(int age) {
person.setAge(age);
return this;
}
public PersonBuilder setAddress(String address){
person.setAddress(address);
return this;
}
public PersonBuilder setEmail(String email){
person.setEmail(email);
return this;
}
public Person build(){
return person;
}
}
To create a Person, a client need to supply required attributes (firstName
and lastName
) into constructor of PersonBuilder
, and optionally call “setter”s of the builder that internally delegate to the Person
class. At last, when the client call build()
, it receives the Person
object with read-only access.
In Ruby
Builder may not even be necessary
The same problem can be solved in an incredibly simple way without builder getting involved at all, thanks to Ruby’s keyword arguments:
class Person
attr_reader :first_name, :last_name, :age, :address, :email
def initialize(first_name:,
last_name:,
age: 0,
address: 'default address',
email: 'default@example.com')
@first_name = first_name
@last_name = last_name
@age = age
@address = address
@email = email
end
end
And client can pass keyword arguments with all required fields and with/without any optional fields:
person = Person.new(first_name: 'John', last_name: 'Doe', email: 'test@example.com')
puts "Hi! My name is #{person.first_name} #{person.last_name}."
puts person.inspect
# Hi! My name is John Doe.
# <Person:0x00007fb96a1aa420 @first_name="John", @last_name="Doe", @age=0, @address="default address", @email="test@example.com">
Builder pattern can be applied in Ruby, of course, especially when the members are complex objects, rather than just primitive types. It’s just that our use case is too simple to worth a dedicated builder class.
Additional Reading
Credit: featured image source: link