28 December 2014

A Simple Argument for Immutable Objects

You have an integer value 4 stored in the variable x.

int x = 4;

You change x.

++x;

In doing so, you have not changed 4 into 5, you have merely changed x to refer to the value 5.

Now suppose you have a class.

class Person {
…
};

Instances of this class are values representing states of an object in your problem domain.

class Person {
private:
string name;
int year_of_birth;
…
};

You want to ensure that these values always represent valid object states. You can do this by prohibiting invalid state transitions.

class Person {
private:
string name;
int year_of_birth;
public:
void set_name(const string& name) {
if (name.empty())
throw invalid_argument("name must be non-empty");
this->name = name;
}
void set_age(const int year_of_birth) {
if (year_of_birth <= 0)
throw invalid_argument("age must be positive");
this->year_of_birth = year_of_birth;
}
…
};

You can also do this by prohibiting the construction of invalid states, and prohibiting all state transitions.

class Person {
public:
const string name;
const int year_of_birth;
Person(const string& name, const int year_of_birth)
: name(name), year_of_birth(year_of_birth) {
if (name.empty())
throw invalid_argument("name must be non-empty");
if (year_of_birth <= 0)
throw invalid_argument("age must be positive");
}
};

Now the values are correct by construction and immutable. All of your validation logic is centralised, so you can verify it in one place. When you want to represent a new state, you simply construct a new value to represent it.

auto me = make_shared<Person>("Jonathan", 1991);
me = make_shared<Person>("Jon", me->year_of_birth);

Creating a new Person to represent the person I am after adopting a nickname does not change the Person I was before adopting a nickname. I am only changing what me refers to.

Whereas a mutable object pretends to be a state, an immutable object represents a state. The latter is not only simpler to reason about, but also more honest about what software is actually doing: representing.