← Back to context

Comment by rezonant

5 years ago

I don't think the two versions are relevant to Law of Demeter. One example has pointers/references in a strong tree and another has indexed ones, but neither is embracing LoD more or less than the other.

This would be a more relevant example:

parent_entity.children.remove(this)

vs

parent_entity.remove_child(this)

...Where remove_child() would handle removing the entity from `children` directly, and also perhaps busting a cache, or notifying the other children that the heirarchy has changed, etc etc.

Going back to your original case, you _could_ argue that LoD would advise you to create a method on entity which returns the parent, but I think that would fall under encapsulation. If you did that though, you could hide the implementation detail of whether `parent` is a reference or an ID on the actual object, which is what most ORMs will do for you.

Ah, but what if children is some kind of List or Collection which can be data-bound? By Liskov's substition principle, you ought to be able to pass it to a Collection-modifying routine and have it function correctly. If the parent must be called the children member should be private, or else the collection should implement eventing and the two methods should have the same effect (and ideally you'd remove one).

  • That takes us back up to viardh's concluding remark from earlier in the thread:

    > You'll of course, as always, find people that takes this way too far. But the general principle is pretty much just to consider where it makes sense to hide linked objects behind a special purpose interface vs. exposing them to clients.

    I would say that if you're using a ViewModel object that will be data-bound, then you're sort of outside the realm of the Law of Demeter. It's really more meant to concern objects that implement business logic, not ones that are meant to be fairly dumb data containers.

    On the other hand, if it is one that is allowed to implement business logic, then I'd say, yeah, making the children member public in the first place is violating the law. You want to keep that hidden and supply a remove_child() method instead, so that you can retain the ability to change the rules around when and how children are removed, without violating LSP in the process.

    • In the other branch I touched on this- iterating children still being a likely use case after all, you have the option of exposing the original or making a copy which could have perf impacts.

      But honestly best to not preoptimize, I would probably do

      private _children : Entity[];

      get children() { return this._children.slice(); }

      And reconsider the potential mutation risk later if the profiler says it matters.

      2 replies →

FWIW, I was writing JavaScript in that example, so `entity.parent` might have been implemented internally as a function anyway:

    get parent() {
        return this.entities.find(this.parent_id);
    }

I don’t think whether we write `entity.parent` or `entity.parent()` really matters to the argument, though.

In any case, I see what you’re getting at. Perhaps a better way of expressing the distinction I was trying to make is whether the nested object that is being accessed in a chain can be freely used without violating any invariants of the immediate object. If not, as in your example where removing a child has additional consequences, it is probably unwise to expose it directly through the immediate object’s interface.

  • Yes, its a great case for making the actual `children` collection private so that mutation must go through the interface methods instead. But still, iteration over the children is a likely use case, so you are left with either exposing the original object or returning a copy of the array (potentially slower, though this might not matter depending).

    • That problem could potentially be solved if the programming language supports returning some form of immutable reference/proxy/cursor that allows a caller to examine the container but without being able to change anything. Unfortunately, many popular languages don’t enforce transitive immutability in that situation, so even returning an “immutable” version of the container doesn’t prevent mutation of its contained values in those languages. Score a few more points for the languages with immutability-by-default or with robust ownership semantics and support for transitive immutability…

      1 reply →