Learning to K.I.S.S (part 2)

In the last post we discussed the importance of immutable objects in a code base and took a critical look at some common techniques for creating immutable objects.  In this post we’ll be looking at some design patterns/ idioms that try to get around those problems whilst still maintaining immutability.  I highly suggest reading the previous post, found here before continuing.

The Parameter/ Data/ Value Object (aka The Object of many names)

In the previous post we saw some problems with passing multiple arguments through in a constructor; that it is difficult to read and prone to error.  That code can be re-factored using ‘Introduce Parameter Object’ you can find more information on this in Martin Fowlers refactoring guide.

  1. Create a value object which temporarily holds all the values required to initialize your object.
  2. Pass this object into the constructor of your object
  3. Set private fields using the data object.
  4. Don’t keep a reference to the data object.

Note step 4: a common mistake would be to hold a reference to the value/ parameter object, however this would void your mutability because an external class could set values on the parameter object and make returned values unpredictable.

This technique is considered fairly expensive because with every new immutable object you must also create a value/ parameter object.  In addition this method does not hide away the implementation details like a factory would, however it can be used nicely in conjunction with a factory.  This pattern does encourage code reuse as VO’s can be pooled if they are frequently used, or using something like the flyweight pattern.

/**
* An example showing incorrect usage of the Value Object pattern
*/
public class MonsterController {

    private var monster : MonsterVO;

    public function MonsterController (monster : MonsterVO)
    {
        this.monster = monster;
    }
}

public class GameController {

    public function GameController()
    {
        const monster : MonsterVO = new MonsterVO();
        monster.setIsScary(true);
        monster.setName("trulyScaryMonster");

        new MonsterController(monster);
        monster.setIsScary(false); //not immutable, we've just set scary after construction!
    }
}

/**
* An example showing correct usage of the Value Object pattern
**/
public class MonsterController {

    private var name : String;
    private var isScary : Boolean
    public function MonsterController (monster : MonsterVO)
    {
         name = monster.getName();
         isScary = monster.getIsScary();
        //aha any changes to the monster object will not affect this class!
    }
}

The builder

The builder by name is designed for construction only and uses a fluent interface to build an object.  It abstracts complex construction logic and produces an immutable object once built. The pattern adds a layer of security to your code; input values may be validated before an object is constructed. In my opinion this is a better solution than a value object because of the incredibly descriptive interface and validation.

This pattern does not need to add superfluous code to the codebase; since the builder and its object are often tightly coupled, it is fine to create an internal class. The code below is based on the builder pattern described by Joshua Bloch in his book Effective Java. Note that actionscript does not support nested classes, (there are work arounds, see this interesting post!), we can however add classes within the same file and internal fields will be accessible by classes only within the same file.

package com.mindcandy.example.immutability
{
    public class Monster
    {
        private var name : String;
        private var isScary : Boolean;

        public function Monster(builder : MonsterBuilder)
        {
            name = builder.name;
            isScary = builder.isScary;
        }

        public static function getBuilder() : MonsterBuilder
        {
            return new MonsterBuilder();
        }

        public function toString() : String
        {
            return " test Monster: " + this.name + " is scary : " + this.isScary;
        }
    }
}

import com.mindcandy.util.preconditions.checkNotNull;
import com.mindcandy.example.immutablity.Monster;
class MonsterBuilder {
       internal var name : String;
       internal var isScary : Boolean;

       public function setName(name : String) : MonsterBuilder
       {
            this.name = name;
            return this;
        }

        public function setIsScary(scary : Boolean) : MonsterBuilder
        {
            this.isScary = scary;
            return this;
        }

        private function validate() : void
        {
           checkNotNull("name must be set for monster", name);
        }

        public function build() : Monster
        {
            validate();
            return new Monster(this);
        }
    }

Another nice builder style pattern is the Immutem pattern which also delays instantiation of an object until initialization parameters have been set.  In my opinion it is inferior to the builder as it lacks the fluent interface (a major advantage) of a builder.

The immutem pattern cannot be implemented in actionscript because the language does not support nested static classes.  A work around by including a private class in the same file (see example above) would still not work because the ‘nested’ class would not have access to the parents private fields. This pattern highlights some of the limitations of actionscript in comparison with older languages such as Java.

A criticism of these patterns are that they are great for code-generator tools but not so great for humans, I do however, tend to disagree with this statement as they add a great deal of value in terms of readability which far out-weighs the additional cost of maintenance (providing the construction is complex enough to warrant a builder).

Immutability via Bubble Wrap

A number of patterns can be used to wrap up your object in an protective bubble wrapped blanket of immutability.  These patterns are generally more flexible and can be coded to allow different access levels and permissions for the object.

The immutable wrapper interface wraps an object in an interface which provides only getters thus the object appears immutable.  I say appears because the fundamental weakness of this pattern is that objects may be upcast and then mutated.

public interface ImmutableWrapper
{
    function getGrowl() : GrowlType
}

public class MutableObject implements ImmutableInterface
{
    private var growl : GrowlType;

    function getGrowl() : GrowlType
    {
        return growl;
    }

    function setGrowl(growl : GrowlType) : void
    {
        this. growl = growl;
    }
}
//abuse of the interface
const immutableObject : ImmutableInterface = getWrappedObject();
(immutableObject as MutableObject).setGrowl(GrowlType.CHEEKY);

Patterns that deal with composition to filter access do not suffer from the same weakness as the immutable wrapper.  Both the proxy and decorator can be used to wrap a mutable object and limit access to fields, they are not immutable patterns as such, but control data access.  Both are able to control access to an object via composition, by storing a reference to the mutable object the proxy or decorator expose an api which can make decisions as to which method calls to foward to the object.  Note typically the decorator pattern would add responsibilities to an object, whereas the proxy would control access.

These particular patterns become a preferred choice when we are trying to limit mutability by controlling various access levels or mutating according to state.  The protection proxy pattern could control access by an agreed list of friends or by the current state of the application.

Consider a document where users have different access privilidges, i.e read-only, read-write, etc.

public final class DocumentProxy
{
    private var mutableDocument : Mutable;
    private var currentState : StateType = StateType.MUTABLE;

    public function saveText(text : String) : void
    {
        if(StateType.MUTABLE)
        {
            mutableDocument.addText(text);
        }
        else
        {
            application.displayWarning("you do not have write privilidges");
        }
    }
}

At this point we are starting to delve into access patterns which will control access to mutable objects, thus limiting mutability, however the objects are not immutable. Limiting mutability is still a good idea and can lead to high encapsulation, predictable state and cohesivness.  Its beyond the scope of this post to delve into those patterns, but examples include the proxy pattern, friendship and locking patterns where conditions prevent or grant access to an object, perhaps depending on a key or predefined friendship.

Frozen code

Popsical immunity describes an object which may continue to mutate until it is set to a frozen state; at this point the state is frozen to further mutations.  The object may have fields mutated multiple times until this state has set and may be useful in scenarios where we are building up a complex object involving multistage calculations (although usually here a builder would do). Popsical immunity could be achieved using the proxy pattern and would follow the following steps:

  1. Create a mutable object
  2. Mutate the values until required state is reached
  3. Create a proxy for the object
  4. Make the object available only via the proxy and throw away references to the original object

Another popular pattern explicitly sets a locked state and performs validation checks at mutating methods.  This code can be quite messy as the multiple checks can add a lot of noise to the code, although this could be abstracted to a helper method.  The following code is an example of how this check might be achieved, although for the purposes of brevity the null/ undefined checks are incomplete.  Other examples are more explicit in their checks and simply set a ‘frozen’ boolean field.

final class Monster
{
    private var growl : GrowlType;

    public function getGrowl() : GrowlType
    {
        return growl;
    }

    public function setGrowl(growl : GrowlType) : void
    {
        Popsical.setOrThrow(growl, value);
    }
}

//Immutable object helper class
public class Popsical
{
    public static function setOrThrow(property, value) : void
    {
        if(property.isNull)
        {
            property = value;
        }
        else
        {
            Log.warn("could not set property on immutable value + property + value");
        }
    }
}

Personally I don’t think this is a great way to enforce immutability and any of the afore mentioned patterns would be a better substitute.  This method would need very clearly documenting and is likely to confuse an API; how is the programmer to know that this object can be set only once? The implementation of this pattern could lead to either:

  • incomplete construction of objects via the example above
  • or, using the freeze method, objects which are not frozen due to a forgetful programmer creating a very difficult to trace bug.

Documentation

Revisiting the reason why we are striving for immutablity: we are trying to communicate, secure and protect the code by limiting state.  If for some reason immutability is unreasonable to implement or refactor then documentation can provide a fail safe.  Well documented code with descriptive methods can at the very least signal some author intent and warn against mutating an object.

/**
* @param growl this should really only be set once because ...
* TODO : refactor this class to be immutable
*/
public function setGrowl(growl : GrowlType) : void

Your documentation may not get read, but at least you tried!

Summary

Immutable objects won’t save you from every pitfall, but its a good place to start. Not all objects should be immutable : view objects being the first that springs to mind.  However thinking about the implementation of immutable objects should in the very least get you thinking about object encapsulation and SRP.  Code should feel simpler, cleaner and easier to read.

Our dev team prides itself on the simplicity of its code; any level of programmer can jump onto an area of the codebase and quickly start working.  In my opinion this is a worthy ongoing goal : let’s K.I.S.S!

The topics discussed in this blog post are an amalgamation of techniques and ideas discussed by various industry experts which I cannot take credit for, I heartily recommend the books listed below!

Leave a Reply