domingo, 16 de enero de 2011

A better builder pattern for Java

#Note 1: Since this is my first real post, and I'm "constructing" the blog, I wanted to make it about constructors. I'm that clever.
#Note 2: The first version of this text was about 10 pages long and my suggestion was at the very end. And then I realize no one was going to read that much. Rookie mistake. So this is a short version, with my suggestion up front.

What am I suggesting? Let's say you have a tipical User class like

public class UserWithBuilder { 
  private String firstName;
  private String lastName;
  private String loginName;
  private String password;
  //more things to come.... 
 }

When you need to create a User, you should write something like
UserWithBuilder user6 = UserWithBuilder.build()
  .firstName("John")
  .lastName("Connor")
  .loginName("jconnor")
  .password("pass6"); 
Some of you will think "Oh, this is stupid. The static method build returns a User, the firstName method sets the firstName and return the User again, and so one".
Another may think "This is the builder pattern from EffectiveJava".

Well, it certanly looks like that, but it is not like that.

The static method build returns and instance of a new class called FirstNameSetter, which has only one method, firstName.
This method returns an instance of LastNameSetter, which has only one method, lastName.
This method returns an instance of LoginNameSetter, which has only one method, loginName.
This method returns an instance of PasswordSetter, which has only one method, password, which returns the actual user.

What is the point of all that complication, and why I say this is better?
I sustain this pattern has some advantages over using a constructor with one parameter per field, and over a no arguments constructor with setters.

It is better than a no arguments constructor with setters approach because:
  • You can't forget to set a field, because you don't get a user until all fields are filled
  • It's shorter, you don't need to write "user.setXXX" once and again
  • It allows you to use final fields in the User class, and final is the new private (Joshua Bloch is suppossed to said that)
It is better than a constructor with one parameter per field approach because:
  • It's way more readable, you don't need to remember parameter order (I know, in this case nobody will confuse firstName with lastName, but you get the point)
  • It's more IDE friendly. If you don't have the source code of User, or the javadoc, with a classic constructor your IDE will show you something like
    what is the meaning of arg0? what is arg1?
    Using this builder pattern, your IDE will show you something like
    (yes, images are photoshopped and I'm not a graphic designer, I know, I know)
    Even without source code and javadoc, it's way more clear. In each step you only see the method you need to use (ok, ok, and the ones inherited from object, there is nothing I can do about that)

What's the catch?
Well, you need to write a lot of code. You need to create a class for each setter, and this classes are really ugly. Let me show you LoginNameSetter
public static class LoginNameSetter{
  private final String fistName;
  private final String lastName;
  public LoginNameSetter(String firstName, String lastName) {
   this.fistName = firstName;
   this.lastName = lastName;
  }
  
  public PasswordSetter loginName(String loginName){
   return new PasswordSetter(this.fistName, this.lastName, loginName);
  }    
 }
You can see the extra fields that pass information from one Setter to the next one, and the only method you need.

Like the builder from EffectiveJava, you can mix this one with varargs, and get something like
UserWithPhone user6 = UserWithPhone()
  .firstName("John")
  .lastName("Connor")
  .loginName("jconnor")
  .password("pass6").
  .phones("1234-5678", "1234-65432");  
just by writing a method
phones(String... phones){...}
instead of
phones(String[] phones){...}
You can even have multiple varargs!
In order to improve readability you can create several methods for problematic classes like Date
UserComplex userComplex = UserComplex.build()
   .firstName("Michael")
   .lastName("Mann")
   .loginName("mmann")
   .password("pass7")
   .phones("1234-5678", "6789-1234")
   .birthDay(2010, 10, 1);
As I've said before, you need to write a lot of code in order to make it work.
And there is no easy way to handle optional parameters, or default values.

But I think it has some overall advantages and it worth the effort, specially for classes with too many fields.

This trick (creating auxiliar classes with one method for parameter) can be used not only for constructors, but for any method, in order to improve readability and pretend to have named parameters.
As an example, the method in java.net.SocketFactory
createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
could be rewritten to have the form
createSocketToAdress(address, port).fromLocal(localAddress, localPort)
I think this is the idea behind FluentInterfaces, but I may have misunderstood it.

About the code:
You can download a nice Eclipse 3.6 project from here, with two nice example classes called UserWithBuilder and UserComplex. Also there is also a Main class with a usage example of both users.

You will also see a MainOtherOptins class which explores several more ways to create an object in java while setting its fields in a readable way.
Take a look! It's is not like you are going to waste an hour, a couple of minutes and you'll be done.

No hay comentarios:

Publicar un comentario