Mutability

From cdd
Jump to: navigation, search

Mutability is causing a lot of problems in the software industry, which are well described in this excellent paper Out of the Tar Pit.

CDD encourage immutable state and the hypothesis is that information hiding is important for mutable state but less important for immutable state. In a functional language like Clojure, immutability is default and data is exposed. CDD has borrowed this idea by exposing immutable state.

Immutable state

Person person = createPerson.age(30).name("Carl").asPerson();
// The immutable state is exposed which makes switching context easy.
TextPerson textPerson = new TextPerson(person.state);

Mutable state

Person person = createPerson.age(30).name("Carl").asPerson();
// The state is not exposed so the only way to switch context is by letting
// the instance itself switch context or by using some trick like reflection.
TextPerson textPerson = person.asTextPerson();


The use of mutable state is something that should be avoided if possible. The reason is that it makes the code harder to reason about and less stable. If you for example pass an instance of a mutable type (e.g. java.util.Date) to a method, you run the risk that the value will be changed/mutated during the call. This is not a desired behaviour in most situations.

CDD supports both mutable and immutable state but recommends the latter. If ýou nevertheless choose to work with mutable state, it will be a good idea to hide the state by making the state variable private and not expose it outside the behaviour representation! This is easies done by inherit from the generated class XCreator.XMutableBehaviour (where X = e.g. Person):

import static PersonCreator.PersonMutableBehaviour;
import static PersonState.PersonMutableState;
 
public class Person extends PersonMutableBehaviour {
    // Make sure the mutable state is private!
    private PersonMutableState state;
 
    public Person(PersonMutableState state) {
        super(state);
        this.state = state;
    }
 
    // Business methods...
}


...the generated code looks like this:

public static class PersonMutableBehaviour {
    private PersonMutableState state;
 
    public PersonMutableBehaviour(PersonMutableState state) {
        this.state = state;
    }
 
    public void assertThat(boolean condition, String attribute, String errorMessage) {
        if (!condition) {
            ValidationErrors.Builder errors = ValidationErrors.builder();
            errors.addError(state, attribute, errorMessage);
            throw new InvalidPersonStateException(errors.build());
        }
    }
 
    @Override public boolean equals(Object that) {
        if (this == that) return true;
        if (that == null || !(that instanceof PersonMutableBehaviour)) return false;
 
        return state.equals(((PersonMutableBehaviour)that).state);
    }
 
    @Override public int hashCode() {
        return state.hashCode();
    }
 
    @Override public String toString() {
        return getClass().getSimpleName() + state;
    }
}


If you choose to work with immutable state, you will instead inherit from XCreator.XBehaviour (where X = e.g. Person):


import static PersonCreator.PersonBehaviour;
 
public class Person extends PersonBehaviour {
    public Person(PersonState state) {
        super(state);
    }
 
    // Business methods...
}


...generated code:

public static class PersonBehaviour {
    public final PersonState state;
 
    public PersonBehaviour(PersonState state) {
        this.state = state;
    }
 
    public void assertThat(boolean condition, String attribute, String errorMessage) {
        if (!condition) {
            ValidationErrors.Builder errors = ValidationErrors.builder();
            errors.addError(state, attribute, errorMessage);
            throw new InvalidPersonStateException(errors.build());
        }
    }
 
    @Override public boolean equals(Object that) {
        if (this == that) return true;
        if (that == null || !(that instanceof PersonBehaviour)) return false;
 
        return state.equals(((PersonBehaviour)that).state);
    }
 
    @Override public int hashCode() {
        return state.hashCode();
    }
 
    @Override public String toString() {
        return getClass().getSimpleName() + state;
    }
}


A bonus you get when inheriting from XBehaviour or XMutableBehaviour is that you can compare objects from different context:

Person person = createPerson().ssn(123).name("Carl").asPerson();
TextPerson textPerson = createPerson().ssn(123).name("Carl").asTextPerson();
 
System.out.println("Equals?: " + person.equals(textPerson));


output:

Equals: true


< Back          Next >