BuilderBuilder: The Task

Short version:

Given minimal information (package, class name, and collection of fields described by name and type), produce Java source for a data transfer class, including a static inner class that functions as a builder.

The data flow of this task looks like this:

GenerationDataFlow.jpg

Given data in a specified input format, a loader will consume the input to produce a model of the class and fields. A code generator will consume the model and produce Java source. The “direct construction” case allows model instances to be produced by code without the need for source data.

Long version:

Data transfer objects (described here) may be used to pass data across boundaries (between systems via network, between Java and non-Java systems, etc.) Developers supporting the Registrar’s office at Bogus University might create a data transfer class representing a student, beginning as follows:

package edu.bogusu.registration;

public class StudentDTO {
    private String id;
    private String firstName;
    private String lastName;
    private int hoursEarned;
    private float gpa;
    // ... other fields
    // ... construction method(s)
    // ... get methods
}

Constructing an instance of StudentDTO is commonly done in one of two ways:

  1. calling a constructor that has a parameter for every field, or
  2. calling a no-argument constructor, then calling a set… method for every field.

The first approach can minimize clutter in the client code that creates a StudentDTO and, more importantly, makes it easy for StudentDTO to be immutable. But as the number of fields grows, so does the parameter list to the constructor call; it becomes ugly and potentially error-prone. The second approach makes the initialization more explicit, at the cost of losing immutability. For this series, we’re going to go through door number three.

Enter The Builder

We’ll create another class whose job is to construct StudentDTO instances. Making it a static inner class to StudentDTO allows us to keep the construction machinery private, giving us more control over the way clients obtain an instance. It also allows us to re-use the idea without name bloat; any FooDTO can have an associated FooDTO.Builder.

Usage

The code is a bit tedious, so we’ll begin with a sample of the intended usage.

StudentDTO student = StudentDTO.builder()
    .id(ID)
    .firstName(FIRST_NAME)
    .lastName(LAST_NAME)
    .hoursEarned(HOURS_EARNED)
    .gpa(GPA)
    .instance();

The static method StudentDTO.builder() provides an instance of the inner Builder class. There is a method on Builder for each field of StudentDTO. Each of those methods returns the Builder instance, allowing the chaining shown in the sample. Finally, the instance() method returns an instance of StudentDTO

The net effect is that of a constructor method with named parameters; we can provide them in whatever order we wish, and can even do the initialization in stages:

StudentDTOBuilder studentBuilder = StudentDTO.builder()
    .id(ID)
    .firstName(FIRST_NAME)
    .lastName(LAST_NAME);
// some tedious computation of HOURS_EARNED
studentBuilder.hoursEarned(HOURS_EARNED);
// some tedious computation of GPA
studentBuilder.gpa(GPA);
// and finally
StudentDTO student = studentBuilder.instance();

(I’m not suggesting that we should use it that way; just demonstrating the flexibility of the technique.)

The Code

There’s a high level of redundancy, so I’ll post just enough to show how the code is organized; the remainder should be obvious.

package edu.bogusu.registration;

public class StudentDTO {

    private String id;
    private String firstName;
    private String lastName;
    private int hoursEarned;
    private float gpa;

    public static class Builder {
        
        private String id;
        // etc. for all StudentDTO fields
        private float gpa;

        private Builder() {
            // nothing here
        }

        public Builder id(String id) {
            this.id = id;
            return this;
        }

        // similar methods for the other fields

        public Builder gpa(float gpa) {
            this.gpa = gpa;
            return this;
        }

        public StudentDTO instance() {
            return new StudentDTO(
                    id,
                    firstName,
                    lastName,
                    hoursEarned,
                    gpa
            );
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    private StudentDTO(
            String id,
            String firstName,
            String lastName,
            int hoursEarned,
            float gpa
    ) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.hoursEarned = hoursEarned;
        this.gpa = gpa;
    }

    public String getId() {
        return id;
    }

    // all StudentDTO fields have get methods

    public float getGpa() {
        return gpa;
    }

}

Just a few key points before closing:

  • The constructors are private to control instance creation. Client code never uses new... () for either class. In addition, we can later address the question of making sure that all required data are present, so that a “half-baked” instance is never created.
  • Each StudentDTO field is shadowed in StudentDTO.Builder. This represents the “work-in-progress inventory” without creating an inconsistent or incomplete object.
  • The ugliness (and risk) of a zillion-parameter constructor for StudentDTO is hidden from the client. Only the generator needs to see that monster.
  • Much of the redundancy is driven by Java’s approach to explicit, static typing. Code generation will keep the many repetitions of the same information in synch.

That’s enough to get a simple version of the problem on the table. Next time, we’ll begin looking at internal representations: first in Java, then in Haskell, Erlang, and Scala.


Recommended reading:

About these ads
Trackbacks are closed, but you can post a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: