Last Updated on 02 July 2019   |   Print Email
This tutorial introduces the best practices in designing method parameters in which the Builder pattern is an ultimate solution.First, let me ask you some questions:
Do you write methods and theirs parameters in your daily coding?
Do you think you have problems in writing methods?
Have you ever applied good practices to design your method’s parameters?
So read on, as I’m going to share with you some cool techniques employed by the experts to write methods professionally. And you can do that, too.Well, I have read the chapter 7 - Methods, item 40 - Design method signatures carefully in the book Effective Java, so I am eager to share what I have learned with you.You know, there are some rules that tell us how to implement methods wisely.
1. Avoid writing a long list of parameters
Here’s such a method:
public static void showMessageDialog(Component parentComponent,
Object message,
String title,
int messageType,
Icon icon)
This method is taken from the JOptionPaneclass in the javax.swing package. It has 5 parameters - good enough to make us difficult to remember which one is for what. Frankly, this method makes me confused most of the time when I use it, as I always have to double-check its Javadoc to ensure I passed the arguments correctly.Having said that, the best practice is writing as few parameters as possible. Having many parameters is bad.
A method has more than 4 parameters is too much. This should be avoided.A method has two parameters is good.A method has only one parameter is the best.And a method has no parameter is ideal.
2. Avoid writing a sequence of parameters of the same type
Consider the following constructor of the Insets class in the javax.swing package:
Insets(int top, int left, int bottom, int right)
And a usage like this:
Insets inset = new Insets(10, 5, 20, 15);
This kind of method is easy to make confusion. Because all the parameters are integer, I often run into trouble when specifying which one is the left, which one is the bottom, and so on. So I always have to look at the Javadoc to make sure.This is also prone to error because I can put wrong parameters and the code is still compiled. The problem can be realized only at runtime.
3. What if we cannot avoid a long list of parameters?
For example, the following method must have 5 parameters:
void drawString(String text, int x, int y, int fontSize, int fontStyle);
According to Joshua Bloch (the author of Effective Java), there are 3 techniques to solve the problem of long parameters list.
Technique #1: Break it into multiple methods
The drawString() method above can be broken into two methods like this:
void setFont(int fontSize, int fontStyle);
void drawString(String text, int x, int y);
However, the drawbacks of this technique are:
Increase the weight of the class (more methods).
It’s not always easy to break the method.
The client code is somewhat verbose when invoking multiple methods.
Technique #2: Create a helper class with JavaBean style
In this technique, we can wrap all the parameters into a helper class that has getters and setters following JavaBean standard. For example, we can wrap the parameters of the drawString() method above into a TextObject class:
class TextObject {
String text;
int fontSize;
int fontStyle;
int x;
int y;
public void setText(String text) {
this.text = text;
}
public String getText() {
return this.text;
}
// other getters and setters go here…
}
Then pass this object into the drawString() method:
drawString(textObj);
This solution is fairly good to solve the problem of long parameters list. However there are some disadvantages:
The client code may be too wordy as using many setters.
The helper class is mutable, which is not good for being passed into the method (Effective Java, Item 39 - Make defensive copies when needed).
It’s difficult to validate all the parameters in one place since the setters are invoked separately.
4. Technique #3: Using the Builder pattern
This technique combines the previous two techniques with the builder pattern to solve the problem of long parameters list. Consider the following class:
public class Person {
// required fields:
private String name;
private int age;
// optional fields:
private Date birthday;
private String email;
private String phone;
private String address;
private float weight;
private float height;
// getters and setters
}
This Person class has only two required fields: name and age, and the rest are optional.Besides the getters and setters, we tend to implement various constructors like these:
public Person(String name, int age);
public Person(String name, int age, Date birthday);
public Person(String name, int age, Date birthday, String email);
public Person(String name, int age, Date birthday, String email, String phone);
…
and so on, until the last constructor covers all the fields, which is equivalent to 8 parameters. Ouch!Suppose that we want to create a new Person object with full details, we tend to use two ways:
First way: sing the full-parameter constructor:
Person me = new Person("Name", 31, new Date(), "nam@gmail.com", "0123456789", "Hanoi", 65, 172);
8 arguments to pass! Too much to remember which one is for which purpose. Such long list of parameters causes the code hard to read and maintain.
Second way: using setter methods:
Person me = new Person();
me.setName("Name");
me.setAge(31);
me.setBirthday(new Date());
me.setEmail("nam@gmail.com");
me.setPhone("0123456789");
me.setAddress("Hanoi");
me.setWeight(65);
me.setHeight(172);
This looks better than using the full-parameter constructor, however it is too wordy. Also the Person object is mutable, which is not good to pass into the method.Now, let’s see how the Builder pattern can solve the problems above.First, we create a Builder class which is nested inside the Person class:
public class Person {
public static class Builder {
}
}
Remember to remove all the setters of the Person class.Here’s the detailed implementation of the Builder class:
public static class Builder {
private String name;
private int age;
private Date birthday;
private String email;
private String phone;
private String address;
private float weight;
private float height;
public Builder(String name, int age) {
this.name = name;
this.age = age;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder birthday(Date birthday) {
this.birthday = birthday;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public Builder weight(float weight) {
this.weight = weight;
return this;
}
public Builder height(float height) {
this.height = height;
return this;
}
public Person build() {
return new Person(this);
}
}
You see, this builder class has same fields as the enclosing class, but the methods for setting values are different. Take a look at this method:
The interesting point here is that this method returns the Builder itself. Why? It’s for easily chaining multiple invocations together. You will see how in the usage example below.The second interesting point is in the build() method:
public Person build() {
return new Person(this);
}
Look, this method returns a new object of the Person class. We need to make the Person’s constructor private like this:
This is the sole constructor of the Person class, it copies values of the fields from the builder to itself.In addition, we can add code to validate the fields in the build() method before returning the new Person object. For example:
public Person build() {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
return new Person(this);
}
That’s done for the implementation of the Person class’ builder. Now, let’s see how to use this pattern:
Person me = new Person.Builder("Nam", 31)
.email("nam@gmail.com")
.address("Hanoi")
.build();
Wow! This looks more readable than using a full-parameter constructor, right?Let’s see another usage which creates a Person object with full details:
Person you = new Person.Builder("You", 27)
.birthday(new Date())
.email("nam@gmail.com")
.phone("0123456789")
.address("Hanoi")
.weight(62)
.height(170)
.build();
You see? This is very flexible and easy to read, right? The Person object created is now immutable (remember to remove all its setters).This builder pattern solves the problems of long parameters list nicely. It takes more time to implement but it will pay off in the long term as the code evolves.So far I have focused on the implementation of the Builder pattern, for more good practices about methods, consult the Effective Java book.
Nam Ha Minh is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.
nice tutorial, however still there is no way to guarantee at compile time that the required fields name and age will be set. this is because the user may never call that contructor method in the Builder class. Only at run time an exception will be thrown due to the validation rules in the build method of the Builder class
Comments
Thanks!!
public class Sth{
private name;
private age;
public Sth name(String name){
this.name = name;
return this;
}
public Sth age(int age){
this.age = age;
return this;
}
}
and then:
Sth s1 = new Sth().name("Name1").age(20);
Sth s2 = new Sth().name("Name2");