This is an AMP version of the article, its original content can be found here.
How an Immutable Object Can Have State and Behavior?
I often hear this argument against
"Yes, they are useful when the state doesn't change. However, in
our case, we deal with frequently changing objects. We simply
can't afford to create a new
document every time we just
need to change its
title." Here is where I disagree: object
title is not a state of a document, if you need to change it frequently.
Instead, it is a document's behavior. A
document can and must be
if it is a good object,
even when its title is changed frequently. Let me explain how.
Identity, State, and Behavior
Basically, there are three elements in every
identity, state, and behavior. Identity is what distinguishes our
from other objects, state is what a document
knows about itself (a.k.a. "encapsulated knowledge"), and
behavior is what a document can do for us on request.
For example, this is a mutable document:
Let's try to use this mutable object:
Here, we're creating two objects and then modifying their encapsulated
first.equals(second) will return
the two objects have different identities, even though they encapsulate the
toString() exposes the document's behavior—the document can
convert itself to a string.
In order to modify a document's title, we just call its
Simply put, we can reuse the object many times, modifying its internal state. It is fast and convenient, isn't it? Fast, yes. Convenient, not really. Read on.
Immutable Objects Have No Identity
As I've mentioned before, immutability is one of the virtues of a good object, and a very important one. A good object is immutable, and good software contains only immutable objects. The main difference between immutable and mutable objects is that an immutable one doesn't have an identity and its state never changes. Here is an immutable variant of the same document:
This document is immutable, and its state (
title) is its identity. Let's
see how we can use this immutable class
(by the way, I'm using
We can't modify a document any more. When we need to change the title, we have to create a new document:
Every time we want to modify its encapsulated state, we have to modify
its identity too, because there is no identity. State is the identity.
Look at the code of the
equals() method above—it compares documents
by their IDs and titles. Now ID+title of a document is its identity!
What About Frequent Changes?
Now I'm getting to the question we started with: What about performance and convenience? We don't want to change the entire document every time we have to modify its title. If the document is big enough, that would be a huge obligation. Moreover, if an immutable object encapsulates other immutable objects, we have to change the entire hierarchy when modifying even a single string in one of them.
The answer is simple. A document's title should not be part of its state. Instead, the title should be its behavior. For example, consider this:
Conceptually speaking, this document is acting as a proxy of a real-life document that has a title stored somewhere—in a file, for example. This is what a good object should do—be a proxy of a real-life entity. The document exposes two features: reading the title and saving the title. Here is how its interface would look like:
title() reads the title of the document and returns it as a
title(String) saves it back into the document. Imagine a real
paper document with a title. You ask an object to read that title from
the paper or to erase an existing one and write new text over it. This
paper is a "copy" utilized in these methods.
Now we can make frequent changes to the immutable document, and the
document stays the same. It doesn't stop being immutable, since it's
id) is not changed. It is the same document, even though we
change its title, because the title is not a state of the document. It
is something in the real world, outside of the document. The document
is just a proxy between us and that "something." Reading and writing
the title are behaviors of the document, not its state.
The only question we still have unanswered is what is that "copy" and what happens if we need to keep the title of the document in memory?
Let's look at it from an "object thinking" point of view. We have a
object, which is supposed to represent a real-life entity in an
object-oriented world. If such an entity is a file, we can easily
title() methods. If such an entity is an Amazon S3 object,
we also implement title reading and writing methods easily, keeping the
object immutable. If such an entity is an HTTP page, we
have no issues in the implementation of title reading or writing, keeping the object immutable.
We have no issues as long as a real-world document exists and has its
own identity. Our title reading and writing methods will communicate with that
real-world document and extract or update its title.
Problems arise when such an entity doesn't exist in a real world. In that
case, we need to create a mutable object property called
title, read it via
title(), and modify it via
title(String). But an object is immutable,
so we can't have a mutable property in it—by definition! What do we do?
How could it be that our object doesn't represent a real-world entity?
Remember, the real world is everything around the living environment of an object.
Is it possible that an object doesn't represent anyone and acts
on its own? No, it's not possible. Every object is a representative of
a real-world entity. So, who does it represent if we want to keep
it and we don't have any file or HTTP page behind the object?
It represents computer memory.
The title of immutable document #50, "How to grill a sandwich," is stored in the memory, taking up 23 bytes of space. The document should know where those bytes are stored, and it should be able to read them and replace them with something else. Those 23 bytes are the real-world entity that the object represents. The bytes have nothing to do with the state of the object. They are a mutable real-world entity, similar to a file, HTTP page, or an Amazon S3 object.
Unfortunately, Java (and many other modern languages) do not allow direct access to computer memory. This is how we would design our class if such direct access was possible:
Memory class would be implemented by JDK natively, and all other
classes would be immutable. The class
Memory would have direct access
to the memory heap and would be responsible for
operations on the operating system level.
Having such a class would allow us to make all Java classes immutable,
Memory class would explicitly emphasize the mission of an object
in a software program, which is to be a data animator. An object is not
holding data; it is animating it. The data exists somewhere, and it is
static, motionless, stationary, etc. The data is dead
while the object is alive. The role of an object is to make a piece of data
alive, to animate it but not to become a piece of data. An object needs some
knowledge in order to gain access to that dead piece of data. An object
may need a database unique key, an HTTP address, a file name, or
a memory address in order to find the data and animate it. But an
object should never think of itself as data.
What Is the Practical Solution?
Unfortunately, we don't have such a memory-representing class
It looks like language designers didn't get the idea of
alive objects vs. dead data, which is sad. We're forced to mix
data with object states using the same language constructs:
object variables and properties.
Maybe someday we'll have that
Memory class in Java and other languages,
but until then, we have a few options.
In C++ and similar low-level languages, it is possible to access memory
directly and deal with in-memory data the same way we deal with
in-file or in-HTTP data. In C++, we can create that
and use it exactly the way we explained above.
In Java, an array is a data structure with a unique property—it can be modified while being declared as
final. You can use
an array of bytes as a mutable data structure inside an immutable object.
It's a surrogate solution that conceptually resembles the
but is much more primitive.
Avoid In-Memory Data.
Try to avoid in-memory data as much as possible. In some domains, it is
easy to do; for example, in web apps, file processing, I/O adapters, etc.
However, in other domains, it is much easier said than done. For example,
in games, data manipulation algorithms, and GUI, most of the objects animate
in-memory data mostly because memory is the only resource they have.
In that case, without the
Memory class, you end up with mutable objects :(
There is no workaround.
To summarize, don't forget that an object is an animator of data. It is using its encapsulated knowledge in order to reach the data. No matter where the data is stored—in a file, in HTTP, or in memory—it is conceptually very different from an object state, even though they may look very similar.
A good object is an immutable animator of mutable data. Even though it is immutable and data is mutable, it is alive and data is dead in the scope of the object's living environment.