I see the benefits of making objects in my program immutable. When I am really deeply thinking about a good design for my application I often naturally arrive at many of my objects being immutable. It often comes to the point where I would like to have all my objects immutable.
This question deals with the same idea but no answer suggests what is a good approach to immutability and when to actually use it. Are there some good immutable design patterns? The general idea seems to be "make objects immutable unless you absolutely need them to change" which is useless in practice.
My experience is that immutability drives my code more and more to the functional paradigm and this progression always happens:
- I start needing persistent (in the functional sense) data structures like lists, maps etc.
- It is extremely inconvenient to work with cross references (e.g. tree node referencing its children while children referencing their parents) which makes me not use cross references at all, which again makes my data structures and code more functional.
- Inheritance stops to make any sense and I start to use composition instead.
- The whole basic ideas of OOP like encapsulation start to fall apart and my objects start to look like functions.
At this point I am practically using nothing from the OOP paradigm anymore and can just switch to a purely functional language. Thus my question: is there a consistent approach to good immutable OOP design or is it always the case that when you take the immutable idea to its fullest potential, you always end up programming in a functional language not needing anything from the OOP world anymore? Are there any good guidelines to decide which classes should be immutable and which should remain mutable to ensure that OOP does not fall apart?
Just for convenience, I will provide an example. Let's have a
ChessBoard as an immutable collection of immutable chess pieces (extending abstract class
Piece). From the OOP point of view, a piece is responsible for generating valid moves from its position on the board. But to generate the moves the piece needs a reference to its board while board needs to have reference to its pieces. Well, there are some tricks to create these immutable cross-references depending on your OOP language but they are pain to manage, better not have a piece to reference its board. But then the piece cannot generate its moves since it does no know the state of the board. Then the piece becomes just a data structure holding the piece type and its position. You can then use a polymorphic function to generate moves for all kinds of pieces. This is perfectly achievable in functional programming but almost impossible in OOP without runtime type checks and other bad OOP practices... Then, a move is just a function which makes a new board from an old board, again a functional idea having perfect sense but having nothing to do with OOP anymore.