8
\$\begingroup\$

OK, this question may seem a little strange at first; however I'd like to have your comments on it.

Background: I do Java. A lot. Java is a statically typed language, it has means to restrict visibility of instance variables, etc. And as such the builder pattern (see here) is quite adapted to it.

I took this pattern a step further. The builder pattern is a one-step process: once you .build(), you obtain an instance, preferrably immutable; but once you have the instance, there is no going back. The freeze/thaw pattern is a "reversible builder".

I implemented it in Java and this gives the following interfaces, which I use quite a lot in my own projects (one-sided discussion about this pattern here):

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

This pattern means that if you have a frozen instance f, you can obtained a thawed instance of that instance by calling .thaw(), modify it (since .thaw() returns a builder) and freeze it again:

// In java, again
final F newFrozen = frozen.thaw().setX().setY().etc().freeze();

I view this pattern as beneficial for several reasons:

  • thawing an instance gives you a builder with that instance's full state (and you can thaw as many times as you want, in a thread safe manner since frozen instances are immutable by contract);
  • you get all the power of builders on the thawed side.

I use this pattern a lot; I find it useful. Do you? What would be your gripes against it?

Sample implementation of a simple pair:

public final class MyClass
    implements Frozen<Builder>
{
    final int value;

    private MyClass(final Builder builder)
    {
        value = builder.value;
    }

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

    public int getValue()
    {
        return value;
    }

    @Override
    public Builder thaw()
    {
        return new Builder(this);
    }

    public static final class Builder
        implements Thawed<MyClass>
    {
        private int value;

        private Builder()
        {
        }

        private Builder(final MyClass myClass)
        {
            value = myClass.value;
        }

        public Builder withValue(final int value)
        {
            this.value = value;
        }

        @Override
        public MyClass freeze()
        {
            return new MyClass(this);
        }
    }
}

For more complex cases, I generally create both classes in the same package and make instance variables for both package visible (this leaves of course the responsibility on me that what I inject into the frozen part is actually immutable, but I deal with it ;)

EDIT Right now the methods are called .thaw() and .freeze(), to reflect the pattern's intents; do you think of better names?

EDIT 2 Objections from @bowmore: the SRP (Single Responsibility Principle) is violated; this is true: the frozen instance has the added responsibility that it must generate a pre-filled thawed instance. The suggestion here is to create an additional constructor/static factory method/method (pick your poison) on the builder class so that it be able to "swallow" the contents of the frozen instance.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ Very cool, if you pardon the pun. The names thaw and freeze might be more precisely labelled as mutable and immutable, or makeMutable and makeImmutable to avoid any keyword issues. \$\endgroup\$ Commented Jun 11, 2013 at 20:48
  • \$\begingroup\$ Is the interface actually used anywhere? For example, would you ever have a variable of type Fronzen<T>? If not, wouldn't just having the two methods (without the interfaces) work the same? \$\endgroup\$
    – svick
    Commented Jun 11, 2013 at 20:49
  • \$\begingroup\$ @svick in my own projects, certainly: two examples are here (frozen part) and here (thawed part). Right now, customization of my API relies on this pattern. I have been very recently investigating dependency injection (using Guice), so parts of this code may change in the near future depending on my findings in the matter... \$\endgroup\$
    – fge
    Commented Jun 11, 2013 at 21:40
  • \$\begingroup\$ @DaveJarvis do you believe that thaw and freeze may be reserved? I admit that I chose these names because, well, they sounded cool, pardon the pun ;) \$\endgroup\$
    – fge
    Commented Jun 11, 2013 at 21:44
  • 3
    \$\begingroup\$ Wouldn't a copy() method on the builder be simpler, more intuitive, and less of a generics puzzle? e.g. MyObject.newBuilder().copy(previouslyBuiltInstance).setParam1(newValue).build(); \$\endgroup\$
    – bowmore
    Commented Jun 11, 2013 at 22:33

1 Answer 1

1
\$\begingroup\$

Pattern pros and cons :

Pros :

  • easy to use
  • readable (provided you are familiar with the pattern)

Cons :

  • forces a cyclic dependency between builder and the class being built (we're probably not crossing package boundaries, though)
  • moves part of the responsibility of the builder to the class being built, in addition to the responsibility that class already had (violating the SRP)

General remarks :

  • I see no potential clients for the interfaces, i.e. they are little more than marker interfaces.

Conclusion :

In my opinion the pattern adds too little value for what it trades off.

\$\endgroup\$
9
  • \$\begingroup\$ I fail to understand your second objection? The frozen part's only responsibility is to "pre-fill" the builder with its internal state \$\endgroup\$
    – fge
    Commented Jun 12, 2013 at 16:35
  • \$\begingroup\$ yes : it has the added responsibility of "building a builder" \$\endgroup\$
    – bowmore
    Commented Jun 12, 2013 at 16:36
  • \$\begingroup\$ Well, that is the whole point! I fail to see an SRP violation at all here! \$\endgroup\$
    – fge
    Commented Jun 12, 2013 at 16:38
  • \$\begingroup\$ I assume the Frozen implementor has responsibilities other than building a builder. Otherwise, I fail to see the use of a class, that without this pattern would have no responsibilities. \$\endgroup\$
    – bowmore
    Commented Jun 12, 2013 at 16:40
  • \$\begingroup\$ Well yes, but that is not the point: if a class chooses to implement this pattern, it will of course bear this responsibility. However this does not prevent the class from fulfilling its "real" duty. SRP is like every principle, it has its limits ;) \$\endgroup\$
    – fge
    Commented Jun 12, 2013 at 16:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.