April 19, 2016

Why Use a Builder Pattern? Examples of Telescoping Constructors when storing Address data

Let's say you wanted to fill out the Billing Address of a form that has the following fields:
  • Name
  • Street Address 1 
  • Street Address 2
  • City
  • State
  • ZipCode
  • Country

If you wanted to store this information into an Address object, the class could look like:

Address.java:
     

public class Address {

  private String name;
  private String streetAddress1;
  private String streetAddress2;
  private String city;
  private String state;
  private String zipCode;
  private String country;
  
}

How would we set these fields?



Exploration: Using Constructors


If we wanted to feed an address such as the one below into an object called Address:

FITBIT
ONE MARINA PARK DRIVE
BOSTON, MA 02210
UNITED STATES

... we could create a constructor that looks like...

 

public class Address {

  private String name;
  private String streetAddress1;
  private String streetAddress2;
  private String city;
  private String state;
  private String zipCode;
  private String country;

  Address ( String name, String streetAddress1, String city,
            String state, String zipCode, String country){
    this.name = name;
    this.streetAddress1 = streetAddress1;
    this.city = city;
    this.state = state;
    this.zipCode = zipCode;
    this.country = country;
  }
}

But what if we wanted to add a suite to the address, to make it more accurate?

FITBIT
ONE MARINA PARK DRIVE
SUITE 701 BOSTON, MA 02210
UNITED STATES

Our entire class now would look like:
 

public class Address {

  private String name;
  private String streetAddress1;
  private String streetAddress2;
  private String city;
  private String state;
  private String zipCode;
  private String country;

  // First Constructor Type: Used When there is only one Address
  Address ( String name, String streetAddress1, String city,
            String state, String zipCode, String country){
    this.name = name;
    this.streetAddress1 = streetAddress1;
    this.city = city;
    this.state = state;
    this.zipCode = zipCode;
    this.country = country;
  }

  // Second Constructor Type: Used When there is Address1 and Address2.
  Address ( String name, String streetAddress1, String streetAddress2,
            String city, String state, String zipCode, String country){
    this.name = name;
    this.streetAddress1 = streetAddress1;
    this.streetAddress2 = streetAddress2;
    this.city = city;
    this.state = state;
    this.zipCode = zipCode;
    this.country = country;
  }
}


Problem One: Duplication of Code


Yes, this is allowed in Java. It is calling Overloading the Method. ( Example )

One problem is that the code is not as clean as it could be... We are no longer following the main rule of software development: Don't Repeat Yourself ( The DRY principle ).

What about if we wanted to mail a package from a warehouse in the Unites States to someone in the United States? We wouldn't need to fill out the country... would we need to create yet another overloaded constructor for the country?

 

// Third Constructor Type: Used Within Same Country
Address ( String name, String streetAddress1, String streetAddress2,
            String city, String state, String zipCode){

We can see that the other problem is that the signature of the Constructor method grows each time the method is overloaded, with even more duplicated code. It becomes a telescoping constructor.

Problem Two: Possible Confusion of Variable Order


Take a look at the following address:

Mr. Address Argentine
Piedras No 623
Piso2 Dto.4
C1070AAM Capital Federal
ARGENTINA

The US Format is:
  • City
  • State
  • Zip
  • Country ( optional in the US )

Argentina ( and many other formats ) are
  • Postal Code
  • City/town/locality
  • Country
These constructors allow you to enter values in the completely wrong order.

 

// Correct Format
Address billingAddress = new Address(
     "FITBIT", "ONE MARINA PARK DRIVE",
     "SUITE 701",
     "BOSTON", "MA", "02210", "United States");

// X INCORRECT FORMAT, but LEGAL!!!
Address shippingAddreess = new Address(
     "Mr. Address Argentine",
     "Piedras No 623",
     "Piso2 Dto.4",    
     "C1070AAM", "Capital Federal",
     "ARGENTINA"); 


To demonstrate what is happening, let's take a step back to figure out how to print the contents of the Address class.

Overriding the toString method that allows us to print to the console.

 

  @Override
  public String toString() {
    return "Name: " + this.name + "\n"
        + "Address1: " + this.streetAddress1 + "\n"
        + "Address2: " + this.streetAddress2 + "\n"
        + "City: " + this.city + "\n"
        + "State: " + this.state + "\n"
        + "Zip: " + this.zipCode + "\n"
        + "Country: " + this.country + "\n";
  }


Now, we can create a test that demonstrate what is happening:

 

  @Test
  public static void testAssigningAddresses() {
    Address billingAddress = new Address(
        "FITBIT", "ONE MARINA PARK DRIVE",
        "SUITE 701",
        "BOSTON", "MA", "02210", "United States");
    System.out.println(billingAddress);

    Address shippingAddress = new Address(
        "Mr. Address Argentine",
        "Piedras No 623",
        "Piso2 Dto.4",
        "C1070AAM", "Capital Federal",
        "ARGENTINA");
    System.out.println(shippingAddress);
  }


The test prints the following output:

 Name: FITBIT  
 Address1: ONE MARINA PARK DRIVE  
 Address2: SUITE 701  
 City: BOSTON  
 State: MA  
 Zip: 02210  
 Country: United States  


 Name: Mr. Address Argentine  
 Address1: Piedras No 623  
 Address2: null  
 City: Piso2 Dto.4  
 State: C1070AAM  
 Zip: Capital Federal  
 Country: ARGENTINA  

... With ARGENTINA, the Zip and the State value was transposed!


Want to take a look at the sample code listed in full? You can find it on my GitHub site at https://github.com/tjmaher/Address_Telescoping_Constructors/blob/master/src/test/java/Address.java.

Introducing the Builder Pattern


Back in 2008, in his book Effective Java, ( Amazon link ) Joshua Block recognized these two problems.

He expanded on the Builder pattern, a software design pattern discussed in the Gang of Four's Design Patterns: Elements of Reusable Object-Oriented Software which addresses these concerns.
"Traditionally, programmers have used the telescoping constructor pattern, in which you provide a constructor with only the required parameters, another with a single optional parameter, a third with two optional parameters, and so on, culminating in a constructor with all the optional parameters [...]

"[...T]he telescoping constructor pattern works, but it is hard to write client code when there are many parameters, and harder still to read it. The reader is left wondering what all those values mean and must carefully count parameters to find out. Long sequences of identically typed parameters can cause subtle bugs. If the client accidentally reverses two such parameters, the compiler won’t complain, but the program will misbehave at runtime.

"Luckily, there is a third alternative that combines the safety of the telescoping constructor pattern with the readability of the JavaBeans pattern. It is a form of the Builder pattern [Gamma95, p. 97]. Instead of making the desired object directly, the client calls a constructor (or static factory) with all of the required parameters and gets a builder object. Then the client calls setter-like methods on the builder object to set each optional parameter of interest. Finally, the client calls a parameterless build method to generate the object, which is immutable".
- From Joshua Bloch's article, Creating and Destroying Java Objects, May 16, 2008.

What if we could set an address like so?:
Address billingAddress = new Address.Builder(
              .setName("Mr. Address Argentine")
              .setAddress1("Piedras No 623")
              .setCity("Piso2 Dto.4")
              .setState("Capital Federal")
              .setZip("C1070AAM")
              .setCountry("ARGENTINA");


In our next blog article, we will be examining how to implement this.




Examining Builder Patterns:


Until then, Happy Testing!

- T.J. Maher
Sr. QA Engineer,
Fitbit-Boston

// QA Engineer since Aug. 1996
// Automation developer for [ 1 ] year and still counting!

No comments: