Immutable objects
In this short article we will see what immutable objects are and why we should consider to use them.
Immutable object is an object that does not change its state after it was created. Immutable objects
usually are very simple. You may already seen them as enum types or primitives like DateTimeImmutable
.
Further in this article you will learn that making simple objects immutable may save significant amount of your time by making impossible to make certain types of mistakes.
When implementing immutable object you need to:
- declare class as
final
so it can’t be overridden adding methods that modify internal state; - declare properties as
private
so, again, state can’t be modified; - avoid setters and use constructor to inject parameters;
- do not store references to mutable objects or collections. If you store collection inside immutable object, it should be immutable too;
- make sure that when you need to modify immutable object you make a copy instead of reusing existing one.
Accidental side effects when changing object in one place results it being unexpectedly changed in another place are quite hard to debug. Such issues could be anywhere: in third party libraries, in language structures, etc. You can avoid these by using immutable object.
So the benefits of using correctly implemented immutable objects are the following:
- program state becomes more predictable since less objects change their state;
- debugging becomes easier because shared references issue isn’t possible;
- it is suitable for concurrent programms (not explained in this article).
Note: Immutability still could be violated via reflection, serialization / unserialization, anonymous function binding or magic methods. Still, all these are not trivial to implement and unlikely to be used by accident.
Let’s get to immutable object example:
<?php
final class Address
{
private $city;
private $house;
private $flat;
public function __construct($city, $house, $flat)
{
$this->city = (string)$city;
$this->house = (string)$house;
$this->flat = (string)$flat;
}
public function getCity()
{
return $this->city;
}
public function getHouse()
{
return $this->house;
}
public function getFlat()
{
return $this->flat;
}
}
This object does not change its state after it was created and thus can be considered immutable.
Example
Now let’s check a case of money transfer where lack of immutability leads to wrong result.
We have Money
class to represent a certain amount of money.
We use it the following way:
Note: Float type only used for example simplicity. In real life you should consider to use bcmath extension or other vendor libraries that provide operations with correct precision.
Everything should be OK. However, as you can see, since Money
class is mutable, Alex will get 2 USD and 6 cents (3% comission) instead of
just 2 USD expected because both $userAmount
and $processedAmount
are referring to the same object. A good way to solve the problem
would be to use Immutable Object.
Instead of modifying existing object either new object should be created or existing one should be copied. Let’s modify code above to create another object:
The approach is OK for simple objects but if initialization is complex, it’s better to start from a copy of existing object:
Usage is the same as it was initially:
In the code above Alex gets intended 2 USD without commission, and Mark is charged correctly with 2 USD plus commission.
Accidental mutability
There are common mistakes that could be made when implementing immutable object in fact making it mutable. These are very important to know and understand.
Leaking internal object reference
We have a mutable class and want to have immutable object that uses it.
Immutable class has getters only, the only property is assigned via constructor. Looks OK, right? Now let’s use these:
Object is still the same. State wasn’t changed. Looks perfect!
Not let’s play with X a bit:
Immutable object state was changed so it’s not really immutable despite being very alike. What happened when implementing it is ignoring “do not store references to mutable objects” rule from the very beginning of this article. So remember that immutable objects should store immutable data or objects only.
Collections
Using collections is common. So what if instead of constructing immutable object with another object we’ll construct it with a collection of objects?
First of all, let’s implement a collection:
Now let’s use it:
As we’ve already learned, having mutable objects inside immutable one is bad so let’s replace mutable objects with scalars.
Since our collection provides method to add more elements we can inderectly change immutable object state. So when working with collection in immutable objects, make sure that collection itself is not mutable i.e. that it contains immutable data only and that there are no methods to add more elements, remove elements or modify collection state in any other way.
Inheritance
Another common case is about inheritance. We know that we should use getters only, instantiate via constructor and store immutable data only inside
immutable object. Let’s modify our Immutable
class to only accept Immutable
objects.
Looks OK… until someone extends your class:
As you can see, it went bad again. This is why immutable objects should be declared as final
to be
never extended.
Conclusion
You’ve learned what is immutable object, when it could be useful and that you should follow a certain set of rules when implementing it:
- declare class as
final
so it can’t be overridden adding methods that modify internal state; - declare properties as
private
so, again, state can’t be modified; - avoid setters and use constructor to inject parameters;
- do not store references to mutable objects or collections. If you store collection inside immutable object, it should be immutable too;
- make sure that when you need to modify immutable object you make a copy instead of reusing existing one.
Authored by Mark Ragazzo, editorial by Alexander Makarov and Vladimir Chub.