I often hear this argument against immutable objects: "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 immutable, 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 object: identity, state, and behavior. Identity is what distinguishes our
document 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 states. Obviously,
first.equals(second) will return
false because the two objects have different identities, even though they encapsulate the same state.
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
setTitle() once again:
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:
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 state (
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
document 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 implement
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
title inside 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
free operations on the operating system level. Having such a class would allow us to make all Java classes immutable, including
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 anemic, 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?
Memory class in Java and other languages, but until then, we have a few options.
Use C++. 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
Memory class and use it exactly the way we explained above.
Use Arrays. 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
Memory class 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.