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.
mutable
andimmutable
, ormakeMutable
andmakeImmutable
to avoid any keyword issues. \$\endgroup\$Fronzen<T>
? If not, wouldn't just having the two methods (without the interfaces) work the same? \$\endgroup\$thaw
andfreeze
may be reserved? I admit that I chose these names because, well, they sounded cool, pardon the pun ;) \$\endgroup\$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\$