Wednesday, October 9, 2013

Too Many Parameters in Java Methods, Part 2: Parameters Object

In my previous post, I looked at some of the problems associated with long parameters lists for methods and constructors. In that post, I discussed replacing primitives and built-in types with custom types to improve readability and type safety. That approached made the numerous parameters to a method or constructor more readable, but did nothing to reduce the number of parameters. In this post, I look at use of a Parameter Object to reduce the number of parameters to a method or constructor.

It is generally not a good idea to make "junk drawer" objects that couple unrelated parameters whose only relationship to one another is that they need to be passed to the same method or constructor. However, when related parameters are being passed to a constructor or method as part of a highly cohesive object, the refactoring known as Introduce Parameter Object is a nice solution. This refactoring's usage is described as "group[ing] of parameters that naturally go together." I will demonstrate this refactoring in this post.

To demonstrate the utility of the Introduce Parameter Object refactoring, let's first look at the example from the last post which uses numerous String and boolean parameters in a method call.

   /**
    * Instantiate a Person object.
    * 
    * @param lastName
    * @param firstName
    * @param middleName
    * @param salutation
    * @param suffix
    * @param streetAddress
    * @param city
    * @param state
    * @param isFemale
    * @param isEmployed
    * @param isHomeOwner
    * @return 
    */
   public Person createPerson(
      final String lastName,
      final String firstName,
      final String middleName,
      final String salutation,
      final String suffix,
      final String streetAddress,
      final String city,
      final String state,
      final boolean isFemale,
      final boolean isEmployed,
      final boolean isHomeOwner)
   {
      // implementation goes here
   }

As I discussed in the previous post, this approach is tedious for callers, makes it all too easy to pass parameters in the wrong order with little type safety, and can reduce readability of the code. Fortunately, the parameters in this example provide some good opportunities to apply the Introduce Parameter Object refactoring. The "names" parameters (including salutation and suffix) could be included in a single full name class. The address parameters (street address, city, and state) could be in a single address object. The other parameters might not be so easily grouped into a single class with high cohesion.

With the suggested applications of the Introduce Parameter Object refactoring, the previously shown method call is simpler thanks to the reduced number of parameters. This is shown in the next code listing.

   public Person createPerson(
      final FullName fullName,
      final Address address,
      final boolean isFemale,
      final boolean isEmployed,
      final boolean isHomeOwner)
   {
      return new Person();
   }

The above example now only has five parameters and is more readable and easier to use by clients. It is also more safe from a typing perspective as it is nearly impossible to confuse strings of names with strings of address in this case. Unfortunately, the three boolean parameters remain a source of potential confusion and cloud readability a bit. The next code listings show potential implementations of the FullName and Address classes.

FullName.java (Simple)
package dustin.examples;

/**
 * Full name of a person.
 * 
 * @author Dustin
 */
public final class FullName
{
   private final String lastName;
   private final String firstName;
   private final String middleName;
   private final String salutation;
   private final String suffix;

   public FullName(
      final String newLastName,
      final String newFirstName,
      final String newMiddleName,
      final String newSalutation,
      final String newSuffix)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.middleName = newMiddleName;
      this.salutation = newSalutation;
      this.suffix = newSuffix;
   }

   public String getLastName()
   {
      return this.lastName;
   }

   public String getFirstName()
   {
      return this.firstName;
   }

   public String getMiddleName()
   {
      return this.middleName;
   }

   public String getSalutation()
   {
      return this.salutation;
   }

   public String getSuffix()
   {
      return this.suffix;
   }

   @Override
   public String toString()
   {
      return  this.salutation + " " + this.firstName + " " + this.middleName
            + this.lastName + ", " + this.suffix;
   }
}
Address.java (Simple)
package dustin.examples;

/**
 * Representation of a United States address.
 * 
 * @author Dustin
 */
public final class Address
{
   private final String streetAddress;
   private final String city;
   private final String state;

   public Address(final String newStreetAddress, final String newCity, final String newState)
   {
      this.streetAddress = newStreetAddress;
      this.city = newCity;
      this.state = newState;
   }

   public String getStreetAddress()
   {
      return this.streetAddress;
   }

   public String getCity()
   {
      return this.city;
   }

   public String getState()
   {
      return this.state;
   }

   @Override
   public String toString()
   {
      return this.streetAddress + ", " + this.city + ", " + this.state;
   }
}

Although the code is improved, there are still some issues that can be improved. In particular, the original method with too many parameters still has three boolean parameters that can be easily confused with one another. Although the String parameters to that method were factored into two new classes, those two new classes still each consist of a bunch of Strings. In these cases, one might want to supplement the Introduce Parameter Object refactoring with use of custom types. Using the custom types I showed in my last post, the method with too many parameters now looks like that shown in the next code listing.

   public Person createPerson(
      final FullName fullName,
      final Address address,
      final Gender gender,
      final EmploymentStatus employment,
      final HomeownerStatus homeownerStatus)
   {
      // implementation goes here
   }

The method now has fewer parameters and the parameters it does have are all of distinct types. IDEs and the Java compiler can now be especially helpful in ensuring that clients use this interface properly. Applying custom types (written in last post) to the FullName and Address classes result in the next two new code listings for those classes.

FullName.java (Custom Types)
package dustin.examples;

/**
 * Full name of a person.
 * 
 * @author Dustin
 */
public final class FullName
{
   private final Name lastName;
   private final Name firstName;
   private final Name middleName;
   private final Salutation salutation;
   private final Suffix suffix;

   public FullName(
      final Name newLastName,
      final Name newFirstName,
      final Name newMiddleName,
      final Salutation newSalutation,
      final Suffix newSuffix)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.middleName = newMiddleName;
      this.salutation = newSalutation;
      this.suffix = newSuffix;
   }

   public Name getLastName()
   {
      return this.lastName;
   }

   public Name getFirstName()
   {
      return this.firstName;
   }

   public Name getMiddleName()
   {
      return this.middleName;
   }

   public Salutation getSalutation()
   {
      return this.salutation;
   }

   public Suffix getSuffix()
   {
      return this.suffix;
   }

   @Override
   public String toString()
   {
      return  this.salutation + " " + this.firstName + " " + this.middleName
            + this.lastName + ", " + this.suffix;
   }
}
Address.java (Custom Types)
package dustin.examples;

/**
 * Representation of a United States address.
 * 
 * @author Dustin
 */
public final class Address
{
   private final StreetAddress streetAddress;
   private final City city;
   private final State state;

   public Address(final StreetAddress newStreetAddress, final City newCity, final State newState)
   {
      this.streetAddress = newStreetAddress;
      this.city = newCity;
      this.state = newState;
   }

   public StreetAddress getStreetAddress()
   {
      return this.streetAddress;
   }

   public City getCity()
   {
      return this.city;
   }

   public State getState()
   {
      return this.state;
   }

   @Override
   public String toString()
   {
      return this.streetAddress + ", " + this.city + ", " + this.state;
   }
}

All of my examples thus far have been of standalone public classes. I often find that if I need a parameter object simply for passing information between methods and constructors in the same package that it can be useful to make these parameter object classes package scope. Nested classes can also be used for these parameter objects in some cases.

Benefits and Advantages

The most obvious benefit of the parameter object is the reduction in number of parameters passed to a method or constructor. This encapsulation of related parameters makes it easier to quickly ascertain what types are being passed to the method or constructor. It is easier for a developer to understand a smaller number of parameters.

Parameter objects share one of the same benefits provided by custom types: the ability to add additional behaviors and characteristics to the parameter object for convenience functions. For example, having an Address class rather than a bunch of String types allows one to validate addresses.

Costs and Disadvantages

The primary drawback to the parameter object is a little extra work to design, implement, and test the class. However, these are pretty easy to write and test and modern tools such as IDEs and scripting languages make it even easier to automate the most mundane and tedious portions of those tasks. An even smaller argument against this approach is that it can be abused. If a developer starts bundling unrelated parameters together into a class just to reduce the number of parameters, that doesn't necessarily help the situation. That approach does indeed reduce the number of parameters, but the ultimate goal of improving readability is not achieved and it could be argued that this approach is even less readable.

Conclusion

Parameter objects provide a nice clean approach to appropriately encapsulating related parameters to reduce the total parameter count to a method or constructor. They are easy to implement and can significantly enhance the readability and type safety parameters passed to method and constructor calls. Parameter objects can be enhanced further through the use of custom types as explained in my previous post.

2 comments:

Greg Brown said...

Sounds like a good use case for the Builder pattern.

http://www.informit.com/articles/article.aspx?p=1216151&seqNum=2

@DustinMarx said...

Greg,

Agreed! That approach is going to be the approach outlined in the forthcoming Part 3.