An object’s state, describes its current condition. If object was a human, this could be their emotional state (e.g happy, sad, angry). To think a bit further, the height value in an object’s field is not a state, but the height’s value help us to define it’s height category (e.g tall, short, average), that is actually the state. And it is the state, based on the context we, as society, have set on defining when someone is considered tall or not.
An OO software system is an objects graph. Objects exchange messages, and based on the message content an object changes it’s state or not. Other objects do not directly read or write the state of another object. The state itself, must never leak out of the object to another. In short, objects do not share any references to state, but instead only report to each other with copies of stateful data. In this fashion, each object is the sole manager of its own state.
But this only half of the story. If we are too strict with the state management, we will not finally have a graph of objects, but rather a strict flow of composition (object B has no meaning or purpose in the system without A) or aggregation (B exists independently (conceptually) from A).
The state will be concentrated to a root object, itself composed by a number of stateful objects, which in turn may be composed of stateful objects. The objects of this hierarchy may pass messages to their immediate children, but not to their ancestors, siblings, or further descendants.
How fragile is the object’s state?
I am sure, that all of us, have worked with source code of a flexible or nonexistent hierarchical pattern at all. We need indeed to be (maybe more) flexible in our everyday work, and the source code is a part of it, but we might end up with objects leaking state.
But a wonderful clean hierarchy is needed when the specifications ask for it. While we organize our objects into a specific hierarchy, the state management is enforced to follow the same pattern. If our business logic is something trivial, probably we will have no trouble at all.
How do we handle use cases that involve multiple objects that are not directly related? One solution could be a common ancestor, other possible solutions is to use the Mediator pattern of the actor model. The larger the software, the more complexity there is, so we have to be really careful with the objects state management. Shallow hierarchies as possible, allow us to successfully manage state . For more information please read here.
Types of immutability
Object itself has no fields or properties that can be changed. This object will remain totally unchanged through its life cycle. For reference types the field must be read only and the type must also meet the immutable guidelines. Types that meet the immutable guidelines, are inherently thread safe. They are not subject to race conditions because they cannot be changed and thus viewed differently between threads .Jared Parsons
The direct contents of this object must be immutable in the sense that they cannot be changed to point to another object. However there is no guarantee that all of the fields are themselves immutable. All of the fields in the object must be read only. For primitive values this is enough to guarantee them meeting the Immutable guidelines and hence Shallow Immutable. For reference types this will ensure they will point to the same object thus doing all that is needed to meet the Shallow Immutable Guarantee. Types with this guarantee are thread safe to a degree. They are thread safe as long as you are accessing the fields with meet the Immutable guarantee. You can also access the references which are read only as long as you do not call any instance methods. For instance a Null check is thread safe .Jared Parsons
Shallow Safe Immutable
Slightly stronger guarantee than Shallow Immutable. For all fields that are read only but not immutable, the type must be thread safe. Types which meet this guarantee are thread safe also to a degree. They are as thread safe as the fields which are guaranteed to be thread safe .Jared Parsons
Benefits and a drawback of immutability
No matter if we decide to use immutability a lot, or enforce it, or use it partially, the goal has always to be the minimization of state usage. Immutability is not a binary decision. It depends on the requirements we have in hand. There is not a specific decision threshold on picking strict immutability or not. The benefits of immutable objects could be summarized to the following (probably incomplete list):
- Immutability prevents null references
- Immutable objects are thread safe
- Immutable objects fight temporal coupling
- Immutable objects have have failure atomicity*
- Immutable objects always return new objects and not copies
- Immutable objects and caching. The cached object is the same with the one in the execution flow. It doesn’t change.
* An immutable object throws an exception, it will never result with the object left in an indeterminate state. This term was coined by Joshua Bloch.
However, if a modified version of an immutable object is needed, we have to suffer the penalty of creating a new object. This must be handled with care in terms of Garbage Collection (GC).
Examples of immutability
Instead of writing numerous examples, I provide you Jon Skeet‘s excellent presentation about immutability. He explains, in details with source code examples, whatever is mentioned, more or less, in this article.
And a bonus video regarding the fundamentals and how data structures affect the way we develop. They effect immutability as part of the big picture.
Immutability is like a big ship. Skills are needed to pilot it, but you have to be careful. A good solid knowledge of OOP is necessary. Try to keep the objects graphs as small as possible, and minimize the dependencies between the objects. We will discuss about the degree of dependency in a future article.
The next episode
In the next episode, we will discuss again about the object’s state and its (im)mutability and its relationship with Inversion of Control and especially with Dependency Injection.