Law of Demeter and immutability
enterprisecraftsmanship.com
enterprisecraftsmanship.com
1. Make it easier for your users to achieve specific goals through a single method call, instead of having to know & go through a chain of method calls. Ie, instead of having to know that the Dog object has Leg sub-objects, and having to call Dog.getLeg().selectMuscle().contract(), the user can instead just invoke Dog.move(). This enhances simplicity, and makes life easy for your users.
2. Hiding implementation details from your users, in order to maintain flexibility for future changes. If all your users are calling Dog.getLeg().selectMuscle().contract(), then you're forced to work with this Dog->Leg->Muscle implementation. On the other hand, if your users are simply calling Dog.move(), then you're free to refactor the class internal structure in dramatic ways.
The fact that an object is immutable, has little impact on the 2 benefits given above. Dementer's Law/Guideline would be well worth paying attention to for those reasons, even when dealing with ImmutableObjects.
https://en.wikipedia.org/wiki/Law_of_Demeter
Whether those are mutable or immutable is irrelevant to the LoD.
Also, your example isn't entirely valid as you mutate the objects' state.
IMO Law of Demeter is not chiefly about state protection -- it's a principle of good design which, when followed, unburdens developers from the cognitive load of keeping the entire universe in their heads. It forces them to think about what is the smallest amount of information this component needs in order to function.
One is that merges are hard (error prone), and mixing concerns results in many more merge conflicts. I 'joke' that half of the tenets of clean code are really about avoiding merge conflicts, but I'm quite serious.
Add to this that Demeter means that you can use local reasoning in many situations, which complements the structure of human working memory. I cannot juggle twelve facts when debugging issues, and Demeter helps you push potentially confounding issues elsewhere where they cannot distract from the matter at hand.
Even in functional programming if you are digging into a data structure 5 levels deep, you are potentially coupling a function to 5 different structures, rather than just one.
For example: game.player.bbox.topLeft.x has the potential to break if the game, player, bbox, or point structure change, which makes it fragile. If using an IP language with inheritance, it could also break if any of their super classes change.
This is why the original formulation of the LoD didn't talk about instances, it talked about classes. You were only allowed to know about the surface-level structure of your neighbours, but anything else of the same class as your neighbours was fair game. So in the `player.Position.X` example, we actually don't have enough information to know if it's a violation or not: we don't know what class `player.Position` is, and we don't know whether it's a legitimate neighbour class. If there's another `Position` field somewhere in scope that's legit, `player.Position.X` is allowed, because we're already coupled to that structure, and getting to it via the `player` object doesn't add any coupling that wasn't there already.
I'm not really sure that much more had to be said than that. There are many instances (especially when you have a data structure that is also a class) where the law of Demeter is a guideline and just that.
However, the caveat is that this:
int positionX = player.Position.X;
Should really look like this: if(player.Position != null) {
int positionX = player.Position.X;
}
In which case having a getter eliminates the need for error handling to be spittled all over the codebase. Having an accessor means you can nip checking for a null reference in the bud. There are other benefits as well. player?.position.x[1] Not for frivolous reasons. It's almost always nonsensical to ask this type of question with "null" values. For the sensical cases we have Maybe/Option.
That makes sense to me too. But compared to Maybe, a null-safe member accessing operator, along with null-lifted operators seems nearly as good from my point of view.
Even so: I'm pretty sure Rust is suitable for game development being essentially zero-overhead, non-GC and Rust effectively disallows "null".
(That, and C++ if you allow only references like another poster mentioned.)
Say you suddenly want to change the position to another unit, or change how it uses position (perhaps return a different position on some cases).
With direct access, you can't.
The intermediate goals should be to reduce coupling, increase encapsulation, etc. These are tools to achieve the end goal.
Multiple dots is just a code smell, indicating that you're probably not meeting your intermediate goals or the end goal. If you see multiple dots, you should probably examine that code to see if it "wants" to be somewhere else, in service of the Primary goal.
But if you elevate "multiple dots" to the level of a "Law" then you're looking at the problem from the wrong end, and are in real danger of losing sight of the end goal. You can follow this law right down a hole that works AGAINST your real goal.
Some say the law is actually a guideline.
I say it's one extreme of a spectrum, and we would do well to aim for the middle.
That's why you subclass -- or compose. The subclass can use them in other ways.
The problem with what you say, that you can't "possibly anticipate all the ways in which objects of the classes involved will be used" is that if you let that happen, then you have to forever support the different ways by which they are used -- and you can't change your implementation internally, make it more efficient etc, because different external code uses various internal details of it.