Skip to content

The freeze thaw pattern

Francis Galiegue edited this page Jun 19, 2013 · 4 revisions

Introduction

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.

Freeze/thaw: an extended builder pattern

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();

Sample implementation

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);
        }
    }
}