Sunday, March 6, 2016

Telescoping and Microscoping Constructor Patterns in Java

Java does not allow default values on constructor parameters, which greatly limits your options for defining constructor overloads. This is undoubtedly not news to anybody who managed to find their way here. But I wanted to take a post and dissect what you do instead.

The Best Way to organize multiple constructors is by applying what is sometimes called the "telescoping constructor" pattern. (See Effective Java, Item #2.) In this pattern, the constructors with the fewest number of parameters progressively call the constructors with larger numbers of parameters, until finally the constructor that takes the most parameters performs the initialization of member variables. (In Scala, we would call this the primary constructor.) It might look like this:

 // Fields (final) go up here...
 
 public Contact(String firstName, String lastName, String phoneNumber, String description,
      String contactType) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.phoneNumber = phoneNumber;
    this.description = description;
    this.contactType = contactType;
  }

  public Contact(String firstName, String lastName, String phoneNumber, String description) {
    this(firstName, lastName, phoneNumber, description, "uncategorized");
  }
  
  public Contact(String firstName, String lastName, String phoneNumber) {
    this(firstName, lastName, phoneNumber, "(no description)");
  }
  
  public Contact(String firstName, String lastName) {
    this(firstName, lastName, "");
  }

Despite what some have argued, I would not classify this as an anti-pattern or something you should never, ever use. A Factory or Builder pattern implementation adds a lot of boilerplate code (which modern Java has enough of already), and not every class is worthy of such a thing. (Although I should point out that my contrived example here probably does warrant a Builder, but I'll save that for later.)

What I really want to rail on here is what I call the "microscoping constructor" pattern, where the "wider" constructors progressively call the narrow ones, culminating with a call to "this()" where all of the fields get set to their default values:

 // Fields (can't be final) go up here...
 
  public Contact() {
    this.firstName = null;
    this.lastName = null;
    this.phoneNumber = null;
    this.description = "(no description)";
    this.contactType = "uncategorized";
  }

  public Contact(String firstName, String lastName, String phoneNumber, String description,
      String contactType) {
    this(firstName, lastName, phoneNumber, description);
    this.contactType = contactType;
  }

  public Contact(String firstName, String lastName, String phoneNumber, String description) {
    this(firstName, lastName, phoneNumber);
    this.description = description;
  }
  
  public Contact(String firstName, String lastName, String phoneNumber) {
    this(firstName, lastName);
    this.phoneNumber = phoneNumber;
  }
  
  public Contact(String firstName, String lastName) {
    this();
    this.firstName = firstName;
    this.lastName = lastName;
  }  

(Please don't ever call "this()" in Java. A kitten dies every time you do that.)

Why are microscoping constructors bad? For one, the code is repetitive insomuch as the same fields tend to get initialized, then reinitialized at least once, and more than once if the code is particularly sloppy. Second, it's not possible to make a class that's doing this into an immutable class. (Hopefully we're all on board with immutable data classes now, right?) If you don't believe me, try combining final fields and microscoping constructors, and the compiler will explain the details.

To sum up:
1. Go ahead and use telescoping constructors.
2. Never use microscoping constructors, and replace them wherever you're unfortunate enough to find them.
3. Consider the Builder pattern as the class becomes complex and/or the constructor parameters' ordering can be ambiguous in terms of their data types.

I'll take more about #3 in a future installment.

No comments: