In this blog post I will show a means to enforce immutability of Smalltalk objects. This is provided by constructing a builder for the intended object which initialises the particular object. An initialisation flag is set after construction to guarantee that the object accessors do not mutate the state after this point.
There are multiple sources on the internet which explain the benefits of immutable objects. Notable examples are:
In Smalltalk instance variable are only visible to the object they belong to. Not even an object of the same class can access them. So in potential Smalltalk objects can truly be immutable.
But this (in)visibility of instance variables poses a problem. How can one create an immutable object with prescribed values for the instance variables?
While pondering this question I came up with the following solution.
In "Design Patterns: Elements of Reusable Object-Oriented Software" the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides) describe the builder pattern.
The builder pattern allows a client to configure all the relevant properties of an object prior to construction. This goal is reached by introducing a helper object which is responsible for recording the preferred properties and is used in the construction of the object.
We will use MyObject class in our examples. The MyObject class has an instance variabe myVariable, which should be initialized to false. We wil now transform this class into a truly immutable object.
Enhance the MyObject class with a new instance variable initialised. For each instance variable, change the getter as follows:
MyObject >> myVariable initialized ifFalse: [ NotInitialisedException new signal ]. ^ myVariableWhere NotInitialisedException is a custom exception class.
Furthermore, for every instance variable, create a one-time setter as illustrated below.
MyObject >> myVariable: value initialized ifFalse: [ myVariable := value ]The combinations of getter, setter and initialised instance variable make sure that all instance variables can be set only once, if the initialised instance variable is set to true after construction. This will be the responsibility of the MyObject class.
A new class method should be introduced.
MyObject class >> newFrom: builder | o | o := MyObject new. o initialiseWith: builder. ^ oMyObject's initialiseWith: method should set the initialised instance variable to false after all the instance variable are retrieved from the builder.
MyObject >> initializeWith: builder initialized ifFalse: [ myVariable := builder myVariable. initialized := true ]I have omitted the code for the builder class. It should have accessors (and backing instance variables) for each instance variable of MyClass. The instance variables of the Builder should be given sensible defaults so that a client does not have to configure a lot prior to construction of a new object.
This wraps up the example of an immutable object in Smalltalk with the help of the builder pattern. It provides a way to force immutability on Smalltalk object by introducing a strict way of accessing instance variables and a record of initialisation state.