The following is the first few sections of a chapter from Android's Architecture Components, plus headings for the remaining major sections, to give you an idea about the content of the chapter.
Generally, developers like setters.
After all, it stands to reason that if you can get a piece of data from an object, you should be able to modify that piece of data in that same object. We are used to read-write data structures, whether in memory or persisted.
However, there are some costs to allowing objects to be mutable (in other words, able to be modified). In some cases, you can have a more robust app architecture if you consider objects to be immutable and unable to be modified. The objects get replaced outright, rather than changing their contents. This winds up being a bit more reminiscent of a transactional database, where changes are applied as a unit, rather than piecemeal.
In this chapter, we will explore the benefits (and costs) of immutability and how to create immutable (or mostly immutable) objects in Java.
Having immutable objects — particularly for things like models — is not a new concept. Immutability has had its adherents for quite some time. It is what lead to libraries like AutoValue for Java, and immutability features in languages like Kotlin.
So, why go with immutability?
A perpetual problem with model objects (and related objects, such as view-models), is knowing what data changed, when, and by what. That comes from promiscuous use of setters, blindly changing data that might be in use already.
Now, we have possible data consistency issues:
Immutable model and view-model objects do not prevent this sort of situation, but they help to make it a bit more obvious. Changing the state is a more obvious action; it is not merely a matter of calling some setters.
If more than one thread has access to the same data, and that data can change,
we wind up having to synchronize access to that data, so that changes can be
made atomically. We do not want a thread to be part-way through updating the
data when another thread tries reading it, as the partly-updated data may be
in an inconsistent state at that moment. We wind up using
CopyOnWriteArrayList and all sorts of other constructs to allow mutable data
to be shared between threads.
This goes away if the data is immutable. Multiple threads can read the same, unchanging data whenever they want without issue. Now, we limit our synchronization to more specific scenarios, such as updating shared caches: so long as we are replacing a cache entry atomically, all consumers of the cache can run in parallel, if the cache entries themselves are immutable.
One way to combat the complexities of multi-threaded development is to use functional programming. Functional programming is based on pure functions: methods (or the equivalent) that operate solely on input parameters, with no side-effects that affect the operation of the program.
RxJava is based on functional programming concepts. We build up chains of RxJava
operators, where each operator applies some sort of function to the input, such
map() operator applying a
Function to convert objects from one type
Immutability is one way of imposing a contract upon yourself, as a developer, to avoid side effects. Calling a setter is a very casual act in programming, even if calling that setter introduces a side effect. Immutability enforces the creation of new objects, ideal for use in pure functions, where the function can create objects to return but cannot change the parameters’ contents and cause side effects.
The preview of this section was traded for a bag of magic beans.
The preview of this section was fed to a gremlin, after midnight.