Pattern 4Replacing Builder for Immutable Object

Intent

To create an immutable object using a friendly syntax for setting attributes—because we can’t modify them, we also need a simple way to create new objects based off existing ones, setting some attributes to new values as we do so.

Overview

In this section, we’ll cover Fluent Builder, which produces immutable objects. This is a common pattern; Java’s standard library uses it with its StringBuilder and StringBuffer. Many other common libraries use it as well, such as Google’s protocol buffers framework.

Using immutable objects are a good practice that’s often ignored in Java, where the most common way of carrying data around is in a class with a bunch of getters and setters. This forces data objects to be mutable, and mutability is the source of many common bugs.

The easiest way to get an immutable object in Java is just to create a class that takes in all the data it needs as constructor arguments. Unfortunately, as item 2 of the excellent Effective Java [Blo08] points out, this leads to a couple of problems when dealing with a large number of attributes.

The first is that a Java class with many constructor arguments is very hard to use. A programmer has to remember which argument goes in which position, rather than referring to it by name. The second is that there is no easy way to create defaults for attributes, since values for all attributes need to be passed into the constructor.

One way to get around that is to create several different constructors that take only a subset of values and that default the ones not passed in. For large objects this leads to the telescoping constructor problem, where a class has to implement many different constructors and pass values from the smaller constructors to ever larger ones.

The builder pattern we examine in this section, outlined in Effective Java [Blo08], solves both of these problems at the cost of quite a bit of code.

Functional Replacement

The techniques used to replace these two patterns in Scala and Clojure are quite different, but both share the very important property that they make it extremely simple to create immutable objects. Let’s take a look at Scala first.

In Scala

We’ll cover three different techniques for creating immutable data structures in Scala, each of which has its own strengths and weaknesses.

First we’ll cover creating a Scala class that consists entirely of immutable values. We’ll show how to use named parameters and default values to achieve something very much like the fluent builder for an immutable object in Java but with a fraction of the overhead.

Next we’ll take a look at Scala’s case classes. Case classes are meant specifically for carrying data, so they come with some handy methods already implemented, like equals and hashCode, and they can be used with Scala’s pattern matching to easily pick them apart. This makes them a good default choice for many data carrying uses.

In both instances, we’ll use Scala’s constructors to create objects. Scala’s constructors don’t have the same shortcomings as the Java constructors we discussed earlier, because we can name parameters and provide them with default values. This helps us avoid both the telescoping constructor problem and the problems involved with passing in several unnamed parameters and trying to remember which is which.

Finally, we’ll cover Scala tuples, which are a handy way to pass around small composite data structures without having to create a new class.

In Clojure

Clojure has support for creating new classes, but it’s intended to be used only for interop with Java. Instead, it’s common to use plain old immutable maps to model aggregate data.

Coming from the Java world, it might seem like this is a bad idea, but since Clojure has excellent support for working with maps, it’s actually very convenient. Using maps to model data allows us the full power of Clojure’s sequence library in manipulating that data, which is a very powerful.

Many libraries rely on inspecting data objects to perform operations on their data, such as XStream, which serializes data objects to XML, or Hibernate, which can generate SQL queries using them. To do this sort of programming in Java, you need to use the reflection library. With Clojure, you can just use simple map operations.

The second way to model data in Clojure is to use a record. A record exposes a map-like interface; so you can still use the full power of Clojure’s sequence library on it, but records have a few advantages over maps.

First, records are generally more performant. In addition, records define a type that can participate in Clojure’s polymorphism. To use the old object-oriented chestnut, it allows us to define a make-noise that will bark when passed a dog and meow when passed a cat. In addition, records let us constrain the attributes that we can put into a data structure.

Generally, a good way to work in Clojure is to start off modeling your data using maps and then switch to records when you need the additional speed, you need to use polymorphism, or you just want to constrain the names of the attributes you’re handling.

Sample Code: Immutable Data

In this section we’ll take a look at how to represent data in Java using a builder for immutable objects. Then we’ll take a look at three ways of replacing them in Clojure: regular classes with immutable attributes, case classes, and tuples. Finally, we’ll take a look at two ways of replacing them in Clojure: plain old maps and records.

Classic Java

In classic Java, we can use a fluent builder to create an immutable object using nice syntax. To solve our problem, we create an ImmutablePerson that only has getters for its attributes. Nested inside of that class, we create a Builder class, which lets us construct an ImmutablePerson.

When we want to create an ImmutablePerson, we don’t construct it directly; we create a new Builder, set the attributes we want to set, and then call build to get an ImmutablePerson. This is outlined below:

 
public​ ​class​ ImmutablePerson {
 
 
private​ ​final​ ​String​ firstName;
 
// more attributes
 
 
public​ ​String​ getFirstName() {
 
return​ firstName;
 
}
 
// more getters
 
 
private​ ImmutablePerson(Builder builder) {
 
firstName = builder.firstName;
 
// set more attributes
 
}
 
 
public​ ​static​ ​class​ Builder {
 
private​ ​String​ firstName;
 
// more attributes
 
 
public​ Builder firstName(​String​ firstName) {
 
this.firstName = firstName;
 
return​ this;
 
}
 
// more setters
 
public​ ImmutablePerson build() {
 
return​ ​new​ ImmutablePerson(this);
 
}
 
}
 
public​ ​static​ Builder newBuilder() {
 
return​ ​new​ Builder();
 
}
 
}

The downside is that we’ve got a whole lot of code for such a basic task. Passing around aggregate data is one of the most basic things we do as programmers, so languages should give us a better way to do it. Thankfully, both Scala and Clojure do. Let’s take a look, starting with Scala.

In Scala

We’ll take a look at three different ways of representing immutable data in Scala: immutable classes, case classes, and tuples. Immutable classes are plain classes that only contain immutable attributes; case classes are a special kind of class intended to work with Scala’s pattern matching; and tuples are immutable data structures that let us group data together without defining a new class.

Immutable Classes

Let’s start by looking at the Scala way to produce immutable objects. All we need to do is define a class that defines some vals as constructor arguments, which will cause the passed-in values to be assigned to public vals. Here’s the code for this solution:

ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/javabean/Person.scala
 
class​ Person(
 
val​ firstName: ​String​,
 
val​ middleName: ​String​,
 
val​ lastName: ​String​)

Now we can create a Person using the constructor parameters positionally:

 
scala>​ val p1 = new Person("John", "Quincy", "Adams")
 
p1: Person = Person@83d2eb1

Or we can use them as named parameters:

 
scala>​ val p2 = new Person(firstName="John", middleName="Quincy", lastName="Adams")
 
p2: Person = Person@33d6798

We can add a default value for parameters, which lets us omit them when using the named parameter form. Here we’re adding a default empty middle name:

ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/javabean/Person.scala
 
class​ PersonWithDefault(
 
val​ firstName: ​String​,
 
val​ middleName: ​String​ = ​""​,
 
val​ lastName: ​String​)

This lets us handle people who may not have a middle name:

 
scala>​ val p3 = new PersonWithDefault(firstName="John", lastName="Adams")
 
p3: PersonWithDefault = PersonWithDefault@6d0984e0

This gives us a simple way of creating immutable objects in Scala, but it does have a few shortcomings. If we want object equality, hash codes, or a nice representation when printed, we need to implement it ourselves. Case classes give us all this out of the box and are designed to participate in Scala’s pattern matching. They can’t, however, be extended, so they’re not suitable for all purposes.

Case Classes

A case class is defined using case class, as shown below:

ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/javabean/Person.scala
 
case​ ​class​ PersonCaseClass(
 
firstName: ​String​,
 
middleName: ​String​ = ​""​,
 
lastName: ​String​)

Now we can create a PersonCaseClass in the same ways we’d create a normal class, except we don’t have to use the new operator. Here we create one using named parameters and by omitting the middle name:

 
scala>​ val p = PersonCaseClass(firstName="John", lastName="Adams")
 
p: PersonCaseClass = PersonCaseClass(John,,Adams)

Notice how the case class prints as PersonCaseClass(John,,Adams), and we didn’t have to implement a toString. We also get equals and hashCode for free with case classes. Here, we test-drive equality:

 
scala>​ val p2 = PersonCaseClass(firstName="John", lastName="Adams")
 
p2: PersonCaseClass = PersonCaseClass(John,,Adams)
 
 
scala>​ p.equals(p2)
 
res1: Boolean = true
 
 
scala>​ val p3 = PersonCaseClass(
 
firstName="John",
 
middleName="Quincy",
 
lastName="Adams")
 
p3: PersonCaseClass = PersonCaseClass(John,Quincy,Adams)
 
 
scala>​ p2.equals(p3)
 
res2: Boolean = false

Case classes are immutable, so we can’t modify them, but we can get the same effect by using the copy method to create a new case class based on an existing one, as we do in the following REPL session:

 
scala>​ val p2 = p.copy(middleName="Quincy")
 
p2: com.mblinn.mbfpp.oo.javabean.PersonCaseClass =
 
PersonCaseClass(John,Quincy,Adams)

Finally, case classes can be used with Scala’s pattern matching. Here we use a pattern match to pick apart the sixth American president:

 
scala>​ p3 match {
 
| case PersonCaseClass(firstName, middleName, lastName) => {
 
| "First: %s - Middle: %s - Last: %s".format(
 
firstName, middleName, lastName)
 
| }}
 
res0: String = First: John - Middle: Quincy - Last: Adams

There’s one final common way to represent data in Scala: tuples. Tuples let us represent a fixed-size record, but they don’t create a new type as classes and case classes do. They’re handy for explorative development; you can use them to model your data during early phases when you’re not sure what it looks like and then switch to classes or case classes later. Let’s take a look at how they work.

Tuples

To create a tuple, you enclose the values that it contains inside of parentheses, like so:

 
scala>​ def p = ("John", "Adams")
 
p: (java.lang.String, java.lang.String)

To get values back out, reference them by position, as we do below:

 
scala>​ p._1
 
res0: java.lang.String = John
 
 
scala>​ p._2
 
res1: java.lang.String = Adams

Finally, tuples can be easily used in pattern matching, just like case classes:

 
scala>​ p match {
 
| case (firstName, lastName) => {
 
| println("First name is: " + firstName)
 
| println("Last name is: " + lastName)
 
| }}
 
First name is: John
 
Last name is: Adams

That covers the three main ways of working with immutable data in Scala.

Plain old immutable classes are handy when you’ve got more attributes than the twenty-two that a case class can handle, though this might suggest that it’s time to refine your data model or that your data objects need to have some methods on them.

Case classes are useful when you want their built-in equals, hashCode, and toString, or when you need to work with pattern matching. Finally, tuples are great for explorative development; you can use them to simply model your data before switching to classes or case classes.

In Clojure

We’ll take a look at two ways to represent immutable data in Clojure. The first is simply storing it in a map, and the second uses a record. Maps are the humble data structure that we all know and love; records are a bit different. They allow us to define a data type and constrain the attributes that they contain, but they still give us a map-like interface.

Maps

Let’s start by taking a look at the simpler of the two options: using an immutable map. All we need to do is create a map with keywords for keys and our data as values, as we do below:

ClojureExamples/src/mbfpp/oo/javabean/person.clj
 
(​def​ p
 
{:first-name ​"John"
 
:middle-name ​"Quincy"
 
:last-name ​"Adams"​})

We can get at attributes as we would with any map:

 
=> (p :first-name)
 
"John"
 
=> (get p :first-name)
 
"John"

One benefit that may not be so obvious is that we can use the full set of operations that maps support, including the ones that treat maps as sequences. For instance, if we wanted to uppercase all the parts of a name, we could do it with the following code:

 
=> (into {} (for [[k, v] p] [k (.toUpperCase v)]))
 
{:middle-name "QUINCY", :last-name "ADAMS", :first-name "JOHN"}

In order to do something similar with objects and getters, we’d need to call all the appropriate getters. That means we’ve taken a solution to a general problem, the problem of capitalizing all the attributes in a data structure full of strings, and reduced its generality to only capitalize the attributes of a particular type, which in turn means we need to reimplement that solution for every type of object.

Using immutable maps as one of the primary ways to carry data around has a few other advantages. Creating them uses simple syntax, so you have no constraints on the attributes you can add to them. This makes them great for exploratory programming.

This flexibility has some downsides. Clojure maps aren’t as efficient as simple Java classes, and once you’ve got your data model more fleshed out, it may help to constrain the attributes you’re dealing with.

Most importantly, however, using plain maps makes it awkward for maps to be used with polymorphism, because using a map doesn’t define a new type. Let’s take a look at another Clojure feature that solves these problems but still presents a map-like interface.

Records

To demonstrate records, let’s borrow an old object-oriented example: creating cats and dogs. To create our Cat and Dog types, we use the code below:

ClojureExamples/src/mbfpp/oo/javabean/catsanddogslivingtogether.clj
 
(​defrecord​ Cat [color ​name​])
 
 
(​defrecord​ Dog [color ​name​])

We can treat them as maps so that we get the full power mentioned above:

 
=> (def cat (Cat. "Calico" "Fuzzy McBootings"))
 
#'mbfpp.oo.javabean.catsanddogslivingtogether/cat
 
=> (def dog (Dog. "Brown" "Brown Dog"))
 
#'mbfpp.oo.javabean.catsanddogslivingtogether/dog
 
=> (:name cat)
 
"Fuzzy McBootings"
 
=> (:name dog)
 
"Brown Dog"

And they can easily participate in polymorphism using Clojure’s protocols. Here we define a protocol that has a single function, make-noise, and we create a NoisyCat and NoisyDog to take advantage of it:

ClojureExamples/src/mbfpp/oo/javabean/catsanddogslivingtogether.clj
 
(​defprotocol​ NoiseMaker
 
(make-noise [this]))
 
 
(​defrecord​ NoisyCat [color ​name​]
 
NoiseMaker
 
(make-noise [this] (​str​ (:name this) ​" meows!"​)))
 
 
(​defrecord​ NoisyDog [color ​name​]
 
NoiseMaker
 
(make-noise [this] (​str​ (:name this) ​" barks!"​)))
 
=> (def noisy-cat (NoisyCat. "Calico" "Fuzzy McBootings"))
 
#'mbfpp.oo.javabean.catsanddogslivingtogether/noisy-cat
 
=> (def noisy-dog (NoisyDog. "Brown" "Brown Dog"))
 
#'mbfpp.oo.javabean.catsanddogslivingtogether/noisy-dog
 
=> (make-noise noisy-cat)
 
"Fuzzy McBootingsmeows!"
 
=> (make-noise noisy-dog)
 
"Brown Dogbarks!"

Those are the two main ways to carry data around in Clojure. The first, plain old maps, is a good place to start. Once you’ve got your data model more nailed down, or if you want to take advantage of Clojure’s protocol polymorphism, you can switch over to a record.

Discussion

There’s a basic tension between locking down your data structures and keeping them flexible. Keeping them flexible helps during development time, while your data model is in flux, but locking them down can help to bring bugs to the surface earlier, which is important once your code is in production. This is mirrored somewhat in the wider technical world with some of the debate surrounding traditional relational databases, which impose a strict schema, and some of the newer nonrelational ones, which have no schemas or have more relaxed schemas, with both sides claiming their approach is better.

In reality, both approaches are useful, depending on the situation. Clojure and Scala give us the best of both worlds here by letting us keep our data structures flexible in the beginning (using maps in Clojure and tuples in Scala) and letting us lock them down as we understand our data better (using records in Clojure and classes or case classes in Scala).

For Further Reading

Effective Java [Blo08]Item 2: Consider a Builder When Faced with Many Constructor Parameters

Effective Java [Blo08]Item 15: Minimize Mutability

Related Patterns

Pattern 19, Focused Mutability

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.137.12