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.