There are some strong reasons why we may want to make genuinely immutable objects. Then there are various frameworks which operate on mutable objects, thus preventing us from using an immutable object pattern. Perhaps we even have a situation where a particular class needs to be mutable, but some of its objects need to be preserved immutably.
We want to avoid the side effects of mutability, which boil down to:
◉ thread leakage
◉ general state degradation of centrally managed objects
◉ unexpected bugs
The options are:
◉ robustly have immutable objects
◉ add a clone function to the object so it can be safely copied for situations where a central copy risks being changed
◉ the thing I’m about to tell you
Looking at having immutable objects for a moment, it seems like it’s a solid enough pattern if you can use it. It’s mistake proof and it does the job. But it’s also a sledgehammer, perhaps even requiring extra code to copy and mutate when something changes.
The clone pattern is great unless someone forgets to use it. Similarly, there’s fluent setters that do copy-on-write and return a new copy, but they don’t work if someone forgets that they don’t mutate the original. These techniques are good, and they’re flawed. Of the pair, the clone is the weakest, as it sort of makes immutability optional, even when it should be mandatory.
The Read-only View
Let’s imagine a simple POJO:
public class Author {
private String name;
private int booksPublished;
private Author[] collaborators;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
... etc
}
The properties of the object may be mutable because we’re either using a serialization framework that uses the getters/setters to do its job, or, shock/horror, the values need to be mutable sometimes. Perhaps something will update the number of books this author has published from time to time.
However, if we had a big list of authors that was being shared around the system, we don’t want something consuming that list being able to change the name or other properties of an author they’re only supposed to be looking at.
Cloning huge collections of data over and over again for processing totally wastes time and memory
We feel this the most, and even get SpotBugs/FindBugs reports, when we return a known mutable object from a getter. For example:
public Author[] getCollaborators() {
return collaborators;
}
// some calling code now can modify the
// internal state of the object!!!
thatAuthor.getCollaborators()[0] = null;
One nice way to provide the outside world with a read-only insight into the contents of mutable collection is to use the Stream API:
public Stream<Author> getCollaboratorStream() {
return Arrays.stream(collaborators);
}
This prevents the caller using a mutable view of the internals.
Let’s Extend To Completely Immutable Views
While my object may itself be immutable, what if its class provided a readonly view as an interface:
interface AuthorReadOnly {
String getName();
int getBooksPublished();
Stream<AuthorReadOnly> getCollaboratorsStream();
}
It would be very easy for our actual POJO to inherit and implement these methods. In fact, the native getters are probably already overrides of these. Any innately mutable object returned could either be returned via its own read-only interface, or returned as a Stream or both.
The beauty of this is that it’s a type trick. There’s very little runtime code to worry about. It’s just a question of how the original object is exposed from a mutable place to an immutable place.
It may help get the best of both worlds sometime.
0 comments:
Post a Comment