Shortly after the Second Edition of Josh Bloch's Effective Java was released, I borrowed a colleague's copy to browse it and consider purchasing a copy of my own despite already owning a copy of the first edition. It did not take me long to realize that the Second Edition was worth purchasing. In particular, the usefulness of the first new item in the Second Edition (Item #2 "Consider a builder when faced with many constructor parameters") struck me. This item addressed many issues I had run into during my Java development in a nice fashion. In this post, I look at the approach described in that chapter and look at how NetBeans 7.2 provides refactoring support for this approach.
As outlined in Item #2 of the Second Edition of Effective Java, there are several disadvantages to using constructors with large parameter lists. To help illustrate, I include a code listing below.
Employee.javapackage dustin.examples; /** * Simple employee class intended to illustrate NetBeans 7.2 and refactoring * constructor to use Builder pattern as discussed in Item #2 of the Second * Edition of Joshua Bloch's <em>Effective Java</em>. * * @author Dustin */ public class Employee { private final String lastName; private final String middleName; private final String firstName; private final long id; private final int birthYear; private final int birthMonth; private final int birthDate; private final int hireYear; private final int hireMonth; private final int hireDate; public Employee( final String newLastName, final String newMiddleName, final String newFirstName, final long newId, final int newBirthYear, final int newBirthMonth, final int newBirthDate, final int newHireYear, final int newHireMonth, final int newHireDate) { this.lastName = newLastName; this.middleName = newMiddleName; this.firstName = newFirstName; this.id = newId; this.birthYear = newBirthYear; this.birthMonth = newBirthMonth; this.birthDate = newBirthDate; this.hireYear = newHireYear; this.hireMonth = newHireMonth; this.hireDate = newHireDate; } }
Employee
's parameterized constructor includes numerous parameters and this presents a challenge to clients of this class that need to create a new instance of it. This particular constructor is particularly tricky for clients because it has three consecutive String
parameters and numerous consecutive int
parameters. It is all too easy for a client to mix up the order of these same-typed parameters in the invocation.
Suppose that business logic is such that an employee should be instantiable with only a last name, first name, and ID provided. In other words, suppose that all attributes of the class are optional except for the three just mentioned. In such a case, with only a constructor like that one above, the client would need to pass null for the middle name String and zeroes or some other number without meaning. The numeric types could be reference types rather than primitives, but the clients would still need to pass null for each optional argument.
One might argue that a three-argument constructor could accepting the three required attributes' values could be supplied and then appropriate set
methods could be invoked to set each optional attribute. However, this approach has drawbacks. For one, it makes it easier for the client to accidentally instantiate an instance with a bad or partial state. In some cases, it is not desirable to change the state of the object after instantiation, but the presence of the set
methods make it more mutable than desired. Indiscriminate supplying of set methods has several drawbacks in the world of concurrency and object-oriented interfaces.
An elegant approach that allows a client to only supply optional parameters that are meaningful without supplying otherwise unnecessary set
methods is the Builder pattern. NetBeans 7.2 makes it easy to refactor the constructor with the long parameter list in the above example to take advantage of an instance of Builder. The next screen snapshot shows how I can use NetBeans 7.2 to accomplish this by hovering over the parameterized constructor, right-clicking on it, and selecting the option "Replace constructor with Builder...".
When this option is selected a popup similar to that shown in the next screen snapshot appears.
The refactoring tool only allows parameters passed to the constructor being refactored to be set in the builder. I can check the boxes of the optional attributes. When I make an attribute optional without supplying a default value, a warning appears as shown in the next screen snapshot.
If I supply default values (null for the String and zero for the integers that should never be zero for valid values), the warnings go away.
It is worth pointing out here that the refactor tool allows the refactor to continue when only warnings (no errors) are present. An error might be caused, for example, by declaring a package and class name of the builder to be generated with a name used by an existing class.
The "Preview" button can be clicked to see what the class would look like without actually creating the class file. When the "Refactor" button is clicked, a new Java class with the package and class name indicated in the "Builder Class Name" text field is created. The result in this case is shown in the next screen snapshot with the generated code listed in a listing following the snapshot.
NetBeans 7.2-Generated EmployeeBuilder.java/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package dustin.examples; public class EmployeeBuilder { private String newLastName; private String newMiddleName = null; private String newFirstName; private long newId; private int newBirthYear = 0; private int newBirthMonth = 0; private int newBirthDate = 0; private int newHireYear = 0; private int newHireMonth = 0; private int newHireDate = 0; public EmployeeBuilder() { } public EmployeeBuilder setNewLastName(String newLastName) { this.newLastName = newLastName; return this; } public EmployeeBuilder setNewMiddleName(String newMiddleName) { this.newMiddleName = newMiddleName; return this; } public EmployeeBuilder setNewFirstName(String newFirstName) { this.newFirstName = newFirstName; return this; } public EmployeeBuilder setNewId(long newId) { this.newId = newId; return this; } public EmployeeBuilder setNewBirthYear(int newBirthYear) { this.newBirthYear = newBirthYear; return this; } public EmployeeBuilder setNewBirthMonth(int newBirthMonth) { this.newBirthMonth = newBirthMonth; return this; } public EmployeeBuilder setNewBirthDate(int newBirthDate) { this.newBirthDate = newBirthDate; return this; } public EmployeeBuilder setNewHireYear(int newHireYear) { this.newHireYear = newHireYear; return this; } public EmployeeBuilder setNewHireMonth(int newHireMonth) { this.newHireMonth = newHireMonth; return this; } public EmployeeBuilder setNewHireDate(int newHireDate) { this.newHireDate = newHireDate; return this; } public Employee createEmployee() { return new Employee(newLastName, newMiddleName, newFirstName, newId, newBirthYear, newBirthMonth, newBirthDate, newHireYear, newHireMonth, newHireDate); } }
As the screen snapshot and code listing above indicate, NetBeans 7.2 generated a builder based on the constructor. This was easily done, but there are a few things I don't like about it as much as the builders I have generated by hand. For the builders I have built by hand, I have chosen to have required attributes be part of the builder's constructor so that they must be supplied (I had expected this to happen for the parameters that I did not check the box for optional on). This is shown in the next code listing, which is an adaptation of the generated code shown above. The only thing changed from above is the builder's constructor. It now takes parameters for the Employee
class's required attributes.
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package dustin.examples; public class EmployeeBuilder { private String newLastName; private String newMiddleName = null; private String newFirstName; private long newId; private int newBirthYear = 0; private int newBirthMonth = 0; private int newBirthDate = 0; private int newHireYear = 0; private int newHireMonth = 0; private int newHireDate = 0; public EmployeeBuilder( final String newBldrLastName, final String newBldrFirstName, final String newBldrMiddleName) { this.newLastName = newBldrLastName; this.newFirstName = newBldrFirstName; this.newMiddleName = newBldrMiddleName; } public EmployeeBuilder setNewLastName(String newLastName) { this.newLastName = newLastName; return this; } public EmployeeBuilder setNewMiddleName(String newMiddleName) { this.newMiddleName = newMiddleName; return this; } public EmployeeBuilder setNewFirstName(String newFirstName) { this.newFirstName = newFirstName; return this; } public EmployeeBuilder setNewId(long newId) { this.newId = newId; return this; } public EmployeeBuilder setNewBirthYear(int newBirthYear) { this.newBirthYear = newBirthYear; return this; } public EmployeeBuilder setNewBirthMonth(int newBirthMonth) { this.newBirthMonth = newBirthMonth; return this; } public EmployeeBuilder setNewBirthDate(int newBirthDate) { this.newBirthDate = newBirthDate; return this; } public EmployeeBuilder setNewHireYear(int newHireYear) { this.newHireYear = newHireYear; return this; } public EmployeeBuilder setNewHireMonth(int newHireMonth) { this.newHireMonth = newHireMonth; return this; } public EmployeeBuilder setNewHireDate(int newHireDate) { this.newHireDate = newHireDate; return this; } public Employee createEmployee() { return new Employee(newLastName, newMiddleName, newFirstName, newId, newBirthYear, newBirthMonth, newBirthDate, newHireYear, newHireMonth, newHireDate); } }
The code used to call the adapted builder shown in the last code listing is exemplified in the following code listing. This code listing demonstrates how readable ("fluent" in the more trendy vernacular) the client code is with the builder and demonstrates that the object is never in a partial or inconsistent state.
package dustin.examples; import java.util.Calendar; /** * Example calling adapted employee builder to get an instance of employee. * * @author Dustin */ public class Main { public static void main(final String[] arguments) { final Employee employee = new EmployeeBuilder("Coyote", "Wile", "E.") .setNewBirthMonth(Calendar.SEPTEMBER) .setNewBirthDate(17) .setNewBirthYear(1949) .createEmployee(); } }
The most significant drawbacks to the builder implementation are potential performance impacts of extra object instantiation (probably not an issue for most Java applications) and the extra code verbosity.
Although I don't show it here, the other thing I like to do with my builder implementations is to include them as nested classes in the class they build. This is how the code example in the second item of the Second Edition of Effective Java implements it, but I don't see anyway in NetBeans 7.2 to generate the builder as a nested class rather than as a standalone class.
Although the NetBeans 7.2-generated builder class is not quite what I have written by hand, it is pretty close and really easy to use. It makes it easy to use an existing parameterized constructor to quickly generate a largely equivalent builder class. Although I have to edit the generated class slightly to get my preferred implementation, it still saves me significant time. Furthermore, this is another example of where an IDE's code generation capabilities help a developer to see how a particular paradigm or pattern is implemented.
2 comments:
My dislikes of this refactoring are exactly the same as yours, so I follow-up by doing those things as addition refactorings. OTOH, the nice thing about it is that it replaces all the old calls to the constructors with appropriate builder methods, so (for me, anyway) it is still a win.
Could you explain about this case : I have a code
public class Vector2F {
public float xpos;
public float ypos;
public static float worldXpos;
public static float worldYpos;
public Vector2F(){
this.xpos=0.0f;
this.ypos=0.0f;
}
public Vector2F(float xpos,float ypos){
this.xpos=xpos;
this.ypos=ypos;
}
public static Vector2F zero(){
return new Vector2F(0,0);
}
public void nomalize(){
double length =Math.sqrt(xpos*xpos+ypos*ypos);
if(length!=0.0){
float s =1.0f /(float)length;
xpos =xpos*s;
ypos =ypos*s;
}
}
public Vector2F getScreenLocation(){
return new Vector2F(xpos,ypos);
}
public Vector2F getWorldLocation(){
return new Vector2F(xpos-worldXpos,ypos-worldYpos);
}
public boolean equals(Vector2F vec){
return (this.xpos==vec.xpos&&this.ypos==vec.ypos);
}
public Vector2F copy(Vector2F vec){
xpos =vec.xpos;
ypos =vec.ypos;
return new Vector2F(xpos,ypos);
}
public Vector2F add(Vector2F vec){
xpos =xpos + vec.xpos;
ypos =ypos + vec.ypos;
return new Vector2F(xpos,ypos);
}
public static void setWorlVariables(float wx,float wy){
worldXpos =wx;
worldYpos =wy;
}
public static double getDistanceOnScreen(Vector2F vec1,Vector2F vec2)
{
float v1=vec1.xpos - vec2.xpos;
float v2=vec1.ypos - vec2.ypos;
return Math.sqrt(v1*v1 + v2*v2);
}
public double getDistanceBetweenWorlVectors(Vector2F vec){
float dx = Math.abs(getWorldLocation().xpos- vec.getWorldLocation().xpos);
float dy = Math.abs(getWorldLocation().ypos- vec.getWorldLocation().ypos);
return Math.abs(dx*dx-dy-dy);
}
}
Does are those line below look like the same as you mention ?
public Vector2F add(Vector2F vec)
public Vector2F getWorldLocation()
public boolean equals(Vector2F vec)
But I am still dont understand why return type and parameter type is Vector2F can you eplain it for me ?
Post a Comment