-
Notifications
You must be signed in to change notification settings - Fork 5
The freeze thaw pattern
The builder pattern is a very useful pattern; builder classes generally returning this
on mutation methods, it allows to write very fluent and expressive code.
Among its advantages:
- argument sanity checking is done in the builder, not in the constructor of the "final product";
- easy to generate immutable instances;
- method names are meaningful;
- can generate subclasses of a base class.
Some hypothetical code would be:
final MyObject o = MyObject.newBuilder()
.setParam1(value1)
.setParam2(value2)
.etc().etc().build();
However, unless you create a bean with a builder (which really destroys the reason for a builder in the first place), the immutability of generated instances can be a problem. This is where the freeze/thaw pattern comes in.
The freeze/thaw pattern extends the builder so that from a generated instance, you can obtain a builder back with all values of the frozen object already configured. In this pattern:
- the builder is the "thawed" side of the pattern, which can be modified; it can create frozen instances;
- the created instance is the "frozen" side of the pattern, which is immutable; it can create thawed instances.
This package provides two interfaces for these:
// Frozen
@Immutable
public interface Frozen<T extends Thawed<? extends Frozen<T>>>
{
/**
* Return a thawed representation of this frozen object.
*
* @return a thawed object
*/
T thaw();
}
// Thawed
@NotThreadSafe
public interface Thawed<F extends Frozen<? extends Thawed<F>>>
{
/**
* Obtain a frozen representation of this thawed object
*
* @return a frozen, immutable object
*/
F freeze();
}
The annotations (they are JSR 305 annotations) further refine the contract:
- frozen instances should always be immutable; that is, you will never have setters on them, they will preferably be
final
, etc etc; - thawed instances offer no guarantee of thread safety.
The code sample above could be therefore modified as this:
final MyObject o1 = MyObject.newBuilder()
.setParam1(value1)
.setParam2(value2)
.etc().etc().freeze();
final MyObject o2 = o1.thaw()
.setParam1(value1)
.setParam2(value2)
.etc().etc().freeze();
This is a simplified implementation of an hypothetical Employee
class:
@Immutable
public final class Employee
implements Frozen<Builder>
{
// All instance variables are final
private final int salary;
// No public constructor, but you could define one if you wanted
private Employee(Builder builder)
{
salary = builder.salary;
}
// Method to return a new builder for this class
public static Builder newBuilder()
{
return new Builder();
}
@Override
public Builder thaw()
{
return new Builder(this);
}
// The builder class
@NotThreadSafe
public static final class Builder
implements Thawed<Employee>
{
private int salary;
// This constructor could be made public
private Builder()
{
}
// This one as well
private Builder(Employee employee)
{
salary = employee.salary;
}
public Builder withSalary(int salary)
{
this.salary = salary;
return this;
}
@Override
public Employee freeze()
{
return new Employee(this);
}
}
}