One useful pattern is to define a public interface which can report all of the properties associated with the class, as well as a package-private interface which extends it with a few additional members. A package-private version of the class should be mutable, but the public-facing one should be immutable. The public-facing class should hold a reference of the private interface type, and implement its members in terms of that private interface. Additionally, all classes in the package must maintain the invariant that no instance of the mutable type may ever be modified after a reference to it has been stored in a field which could be reachable on another thread.
Invoking a method like
WithName(String name) on the public face of the class could cause it to call
WithName on the encapsulated object, which would in most cases constructing a
NameAdder object which holds the new
Name value along with a reference to the encapsulated object itself. All methods other than
WithName would chain to the encapsulated object; while
GetName() would return the new
WithName would return a new
NameAdder object which holds the new
Name and a reference to the object encapsulated in the
NameAdder (rather than a reference to the
An obvious problem with this approach is that more and more operands get performed upon an object, the chains of method invocations would become longer and longer. To mitigate that, the internal interface (and perhaps the public one as well) should include a
Flatten method. Calling
Flatten on an implementation of the interface should yield a new object which applies all of the changes that have been added to it via methods like
WithName. One way of accomplishing that would be to construct a new object where every field's value was computed by calling
Get on all of the objects involved. That could be expensive and inefficient, however, if it gets invoked upon an object that's very "deep".
An alternative approach would be to have the private interface include an
AsFlattenedMutable method which would an instance of the mutable class to which all appropriate changes had been applied, and to which no reference had ever been stored in any class field anywhere. The mutable class could implement
AsFlattenedMutable to return a clone of itself; a class like
NameAdder would implement
AsFlattenedMutable to invoke
AsFlattenedMutable upon the encapsulated object, modify the name stored in that mutable object, and return the object after the mutation. This would thus allow even a rather deep modification chain to be resolved rather quickly.
The only slight "gotcha" with that approach is if code modifies an object and then stores a reference into a non-
final field, the generated code could potentially resequence the operations so that the reference to the object gets stored before all the modifications are complete. To properly solve that may require having a new object store a reference to the mutable object into a
final field, and then copying the reference from that final-field into the non-final one. By my understanding of the JVM specification, storing a reference to an object into a
final field of an object under construction would prevent the deferral of any pending requests beyond the return of the constructor, and reading the value of that field would prevent the non-final field from being assigned until after the execution of the constructor [if code didn't read the reference from the new object, but instead simply used a pre-existing copy, then the call to the constructor--as well as any pending mutations to the object of interest--could be deferred until after the assignment].