In this post, we’ll discuss the Law of Demeter in the context of immutability.
Law of Demeter
The law of Demeter is a guideline saying that objects should talk to their immediate neighbours only. For example, if you want your dog to move, you don’t try to control her legs directly:
Instead, you send her a command and rely on the dog herself to figure out the details:
This guideline is very similar to the Tell Don’t Ask one. In fact, I personally think they are synonyms and aim at essentially the same thing.
Following this guideline is what we, programmers working with OOP languages, learn to do pretty early. And there’s a good reason for that. It fosters proper encapsulation practices and better code maintainability overall. The situation where the client code manually operates the dog’s legs is a clear leakage of implementation details: in order to achieve a single goal – making the dog move – the client needs to perform not one but four different actions.
The law of Demeter is sometimes said to be the guideline of a “single dot”. That is, you are not allowed to dive into your objects for more than one level of depth. In the first code sample, we are doing exactly that: we first query a certain leg from the dog and then dive deeper into that object by executing a command on it.
Law of Demeter and immutability
In general, we can say that any code that looks like the following violates the Law of Demeter:
A a = new A();
Alright, but how about this one?
int positionX = player.Position.X;
Here, the player has a position on a map which is represented by an immutable data structure, and we request the X portion of it. Does this code violate the law of Demeter too? It does because the property X is not an immediate neighbor of the Player class.
At the same time, your intuition most likely tells you that there’s nothing wrong with this particular code sample. It’s definitely better than having all members of Position duplicated in the Player class itself:
int positionX = player.GetPositionX();
int positionY = player.GetPositionY();
/* etc */
So, what is going on here? Is our intuition failing us? Or maybe there’s something wrong with the guideline?
To answer this question, we need to step back and look at the goal the law of Demeter aims at achieving. The main goal here is to assure proper encapsulation. That is, to make sure that the state of the object can only be changed via its public API.
In the example with the dog and her legs, we modify the dog’s state not through an explicit API on its class but via accessing its sub-objects and messing up with them directly. That is a horrible thing to do because such changes can potentially violate the class’s invariants. For example, one of the invariants might state that all four legs should move in the same direction only. However, there’s no way the Dog class can enforce this invariant because it doesn’t have control over how the clients work with its legs.
The key point here is state. That is data that changes over time. We need to make sure that this data is modified correctly and not get corrupted along the way. Basically, all best practices related to encapsulation aim for this exact purpose: to guarantee that any existing state is properly maintained and no invariant is violated during state mutations. When you follow the law of Demeter, it is much easier to accomplish this goal as all changes related to a class go through this class directly and thus can be easily validated.
Now, let’s look at this code sample again:
int positionX = player.Position.X;
Is it somehow different from the example with the dog and her legs? It is. The data structure behind the Position property is immutable, and that means it’s safe to expose it directly as we do in the sample above. The client code is unable to mess up with the position’s internal state because it simply doesn’t have any.
Note that state is not just data that belong to some class, it is subset of that data that changes over time. In that sense, immutable classes don’t contain any state, only mutable classes do.
So, the conclusion here is the following: the law of Demeter is not applicable to the world of purely functional (immutable) data structures. In functional programming, everything can be (and usually is) made public for that exact reason: you don’t need to ever worry about state and its corruption because it’s impossible to corrupt something that cannot be changed in the first place.
Here’s a great quote from Michael Feathers with that regard:
OO makes code understandable by encapsulating moving parts. Functional programming makes code understandable by minimizing moving parts.
Does it mean we can write code like this?
A a = new A();
Or even this?
If all of the members in this chain are represented by immutable classes then yes, we can definitely do that. That’s the essence of functional programming: as long as all your data is immutable, you can do whatever you want with it, be that making all its members public or querying portions of it in this weird way.
Make sure that you don’t duplicate domain knowledge, though. The Dry principle is applicable regardless of the paradigm you choose to program in. In OOP, this place is usually the class that owns the data. However, it doesn’t have to be that way as you don’t need to enforce any invariants in this case. So you can very well separate the data from the code working on that data.
Just wanted to make a remark regarding coupling. Coupling is another important concept of course, but it can be mitigated by following the DRY principle. That is, if you have only one function that knows about the object’s internal structure (“couples” to it, so to speak), it doesn’t really matter where exactly that function is located – in the class itself or in some other place.
- The law of Demeter aims at ensuring that all class’s invariants are honored during its state mutation.
- The law of Demeter is not applicable in scenarios with immutability because there’s no way the internal state can be corrupted.
- We can dive as deep into the class’s internals as we want as long as all data we work with is immutable.